aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTill Glöggler <till@gundk.it>2025-02-27 19:10:07 +0100
committerTill Glöggler <till@gundk.it>2025-02-27 19:10:07 +0100
commit51ad4bd4b8628cd495483924231d141d8a45c6cf (patch)
tree3e3d38781c233845895696468806f846459c8409
parent257f38c62b0847f76fc477c6e014c5d2a0a5bf54 (diff)
testing gitlab ci
-rw-r--r--.gitlab-ci.yml14
-rw-r--r--docs/.gitignore20
-rw-r--r--docs/.gitlab-ci.yml28
-rw-r--r--docs/README.md33
-rw-r--r--docs/babel.config.js3
-rw-r--r--docs/docs/a11y/background.md100
-rw-r--r--docs/docs/a11y/declaration_template.md135
-rw-r--r--docs/docs/a11y/review.md47
-rw-r--r--docs/docs/a11y/skiplinks.md106
-rw-r--r--docs/docs/a11y/start.md223
-rw-r--r--docs/docs/a11y/tips.md181
-rw-r--r--docs/docs/a11y/todo.md285
-rw-r--r--docs/docs/assets/06e2031cec109992fa2df034e5c65642/CLIHelp.pngbin0 -> 207168 bytes
-rw-r--r--docs/docs/assets/0ca19cdf7ae62c47c4a74b0110030059/dummy_plugin-v0.3.zipbin0 -> 1887 bytes
-rw-r--r--docs/docs/assets/13c6d38bf74b403424e42c16f8308bfe/Bildschirmfoto_2021-11-12_um_11.04.40.pngbin0 -> 281731 bytes
-rw-r--r--docs/docs/assets/16a10521ad60ae379906d2c9a8dab608/image.pngbin0 -> 6846 bytes
-rw-r--r--docs/docs/assets/1afde9c989ac66fc23192c4e7ca6aea8/image.pngbin0 -> 102524 bytes
-rw-r--r--docs/docs/assets/1cde32e97e840ad35cd7840e4d61b016/image.pngbin0 -> 94053 bytes
-rw-r--r--docs/docs/assets/1ce06d185917489aa6f3da79f9a491d6/image.pngbin0 -> 7052 bytes
-rw-r--r--docs/docs/assets/30d74c57a521f139c0050de6d55866a7/image.pngbin0 -> 66904 bytes
-rw-r--r--docs/docs/assets/31dccb807d8186506f761674e5637696/Bildschirmfoto_2021-11-12_um_14.12.17.pngbin0 -> 92478 bytes
-rw-r--r--docs/docs/assets/3310a5850c6c4ed2d0b55a1884e5a39b/config_local.inc.php38
-rw-r--r--docs/docs/assets/3a528f8f2de835a0ba5c4e342929179a/image.pngbin0 -> 14842 bytes
-rw-r--r--docs/docs/assets/3dd1c5b5758d79e52a77165d721e1665/image.pngbin0 -> 9223 bytes
-rw-r--r--docs/docs/assets/3ef926a748f8a39a8e44423cbd561786/image.pngbin0 -> 6686 bytes
-rw-r--r--docs/docs/assets/45ecdce4e78d28ce8b9d1f4e42566069/image.pngbin0 -> 4668 bytes
-rw-r--r--docs/docs/assets/476d123391bbc2e4a825a1ea146a8465/image.pngbin0 -> 111455 bytes
-rw-r--r--docs/docs/assets/4bc68dbe8b01745976ff8b18aa025de6/image.pngbin0 -> 110218 bytes
-rw-r--r--docs/docs/assets/4c211f7ead854530fec8791e1f58589e/image.pngbin0 -> 6647 bytes
-rw-r--r--docs/docs/assets/57d2b4e9802cbb4c83816956d7d53a75/image.pngbin0 -> 5711 bytes
-rw-r--r--docs/docs/assets/69756a9f57c09cc3f3450519af957b23/dateiliste.pngbin0 -> 46238 bytes
-rw-r--r--docs/docs/assets/6a365d838099cd2de468d98e0844f780/image.pngbin0 -> 6552 bytes
-rw-r--r--docs/docs/assets/6d14189aa9093eb042bfa56eae8c7dc2/170804_Studip-Farbset.pdfbin0 -> 47696 bytes
-rw-r--r--docs/docs/assets/6feeded7490d702b510a71a924934269/footer.pngbin0 -> 14080 bytes
-rw-r--r--docs/docs/assets/734bff664ced438cfa362a79142ecddb/image.pngbin0 -> 6991 bytes
-rw-r--r--docs/docs/assets/829b531d3dbd3e2bde37443f1394f821/image.pngbin0 -> 2459 bytes
-rw-r--r--docs/docs/assets/8fcb015158becfc136f6d2db84ef27bf/image.pngbin0 -> 1550 bytes
-rw-r--r--docs/docs/assets/a0377c72d0325b7d57151aca9ad52143/dialog.pngbin0 -> 64870 bytes
-rw-r--r--docs/docs/assets/bbe72ca0392b9f0f40989e5fabba6e14/konfiguration.pngbin0 -> 66330 bytes
-rw-r--r--docs/docs/assets/c061aa06f3acdf6003dc54bd1e677ebe/image.pngbin0 -> 2644 bytes
-rw-r--r--docs/docs/assets/c36105bd58464c5eb00e596123af30c8/CLIHelpOnCommands.pngbin0 -> 73647 bytes
-rw-r--r--docs/docs/assets/c37f69398215d78b12784d3428c89a9c/Bildschirmfoto_2021-11-15_um_15.35.11.pngbin0 -> 209083 bytes
-rw-r--r--docs/docs/assets/ce19cb16e52e35fa80ad2fd66ee7fbac/image.pngbin0 -> 13803 bytes
-rw-r--r--docs/docs/assets/d853edb1d90bc82e3dc0d69fcb2927b4/image.pngbin0 -> 99820 bytes
-rw-r--r--docs/docs/assets/dc7253456d69ace6eb97258059ce0a87/image.pngbin0 -> 111146 bytes
-rw-r--r--docs/docs/assets/fbce782c9fa1a8778926c5f6ade1d5d4/image.pngbin0 -> 16989 bytes
-rw-r--r--docs/docs/assets/fe20666fc8035cf25e6e65e3a36bd83b/image.pngbin0 -> 4021 bytes
-rw-r--r--docs/docs/assets/hello_axel.pngbin0 -> 12657 bytes
-rw-r--r--docs/docs/assets/layout.pngbin0 -> 8764 bytes
-rw-r--r--docs/docs/assets/quotes.pngbin0 -> 30809 bytes
-rw-r--r--docs/docs/coding-style.md527
-rw-r--r--docs/docs/functions/actionmenu.md90
-rw-r--r--docs/docs/functions/activity-api.md193
-rw-r--r--docs/docs/functions/admin-search.md126
-rw-r--r--docs/docs/functions/assets.md52
-rw-r--r--docs/docs/functions/baumstrukturen.md58
-rw-r--r--docs/docs/functions/cache.md112
-rw-r--r--docs/docs/functions/cli-plugin-manager.md104
-rw-r--r--docs/docs/functions/cli.md43
-rw-r--r--docs/docs/functions/course-wizard.md98
-rw-r--r--docs/docs/functions/coursesets.md159
-rw-r--r--docs/docs/functions/cronjobs.md102
-rw-r--r--docs/docs/functions/css-z-indizes.md24
-rw-r--r--docs/docs/functions/deputy.md112
-rw-r--r--docs/docs/functions/etask-tables.md315
-rw-r--r--docs/docs/functions/event-logging.md216
-rw-r--r--docs/docs/functions/format.md38
-rw-r--r--docs/docs/functions/global-search-module.md38
-rw-r--r--docs/docs/functions/icon.md170
-rw-r--r--docs/docs/functions/jsupdater.md53
-rw-r--r--docs/docs/functions/less.md71
-rw-r--r--docs/docs/functions/log.md85
-rw-r--r--docs/docs/functions/migrations.md230
-rw-r--r--docs/docs/functions/modaler-dialog.md179
-rw-r--r--docs/docs/functions/multi-person-search.md52
-rw-r--r--docs/docs/functions/new-html-structure.md79
-rw-r--r--docs/docs/functions/notifications.md355
-rw-r--r--docs/docs/functions/oauth2.md90
-rw-r--r--docs/docs/functions/page-layout.md189
-rw-r--r--docs/docs/functions/pdf-exports.md71
-rw-r--r--docs/docs/functions/personal-notifications.md31
-rw-r--r--docs/docs/functions/qr-codes.md16
-rw-r--r--docs/docs/functions/quick-search.md101
-rw-r--r--docs/docs/functions/responsive-navigation.md110
-rw-r--r--docs/docs/functions/studip-form.md377
-rw-r--r--docs/docs/functions/studip-format.md96
-rw-r--r--docs/docs/functions/studip-mail.md65
-rw-r--r--docs/docs/functions/studip-pdo.md91
-rw-r--r--docs/docs/functions/user-lookup.md85
-rw-r--r--docs/docs/functions/visibility.md194
-rw-r--r--docs/docs/functions/wysiwyg.md100
-rw-r--r--docs/docs/getting-started.md28
-rw-r--r--docs/docs/jsonapi/activitystreams.md140
-rw-r--r--docs/docs/jsonapi/blubber.md421
-rw-r--r--docs/docs/jsonapi/contacts.md167
-rw-r--r--docs/docs/jsonapi/courses.md258
-rw-r--r--docs/docs/jsonapi/discovery.md48
-rw-r--r--docs/docs/jsonapi/errors.md17
-rw-r--r--docs/docs/jsonapi/files.md887
-rw-r--r--docs/docs/jsonapi/forum.md626
-rw-r--r--docs/docs/jsonapi/institutes.md165
-rw-r--r--docs/docs/jsonapi/messages.md144
-rw-r--r--docs/docs/jsonapi/news.md633
-rw-r--r--docs/docs/jsonapi/planer.md294
-rw-r--r--docs/docs/jsonapi/resources.md123
-rw-r--r--docs/docs/jsonapi/routen.md299
-rw-r--r--docs/docs/jsonapi/semesters.md40
-rw-r--r--docs/docs/jsonapi/start.md87
-rw-r--r--docs/docs/jsonapi/studip.md48
-rw-r--r--docs/docs/jsonapi/users.md344
-rw-r--r--docs/docs/jsonapi/wiki.md233
-rw-r--r--docs/docs/landing-page.md52
-rw-r--r--docs/docs/plugins/allgemeines.md454
-rw-r--r--docs/docs/plugins/automatic-updates.md42
-rw-r--r--docs/docs/plugins/tutorial-funktion.md620
-rw-r--r--docs/docs/plugins/tutorial-struktur.md588
-rw-r--r--docs/docs/plugins/tutorial.md512
-rw-r--r--docs/docs/quickstart/allgemeine-struktur.md162
-rw-r--r--docs/docs/quickstart/api-dokumentation.md146
-rw-r--r--docs/docs/quickstart/asset-bundling.md80
-rw-r--r--docs/docs/quickstart/buttons.md38
-rw-r--r--docs/docs/quickstart/cheat-sheet.md310
-rw-r--r--docs/docs/quickstart/coding-style.md525
-rw-r--r--docs/docs/quickstart/csrf-protection.md79
-rw-r--r--docs/docs/quickstart/dateitypen.md27
-rw-r--r--docs/docs/quickstart/datenbank.md116
-rw-r--r--docs/docs/quickstart/db-klassen.md29
-rw-r--r--docs/docs/quickstart/einsteiger.md51
-rw-r--r--docs/docs/quickstart/entwicklungsumgebung.md236
-rw-r--r--docs/docs/quickstart/helpbar.md33
-rw-r--r--docs/docs/quickstart/html-ausgaben.md87
-rw-r--r--docs/docs/quickstart/internationalisierung.md222
-rw-r--r--docs/docs/quickstart/javascript.md190
-rw-r--r--docs/docs/quickstart/message-box.md60
-rw-r--r--docs/docs/quickstart/modaler-dialog.md171
-rw-r--r--docs/docs/quickstart/navigation.md255
-rw-r--r--docs/docs/quickstart/ordnerstruktur.md85
-rw-r--r--docs/docs/quickstart/page-layout.md186
-rw-r--r--docs/docs/quickstart/rechtestufen.md6
-rw-r--r--docs/docs/quickstart/request.md125
-rw-r--r--docs/docs/quickstart/responsive-design.md160
-rw-r--r--docs/docs/quickstart/sidebar.md79
-rw-r--r--docs/docs/quickstart/simpleormap.md315
-rw-r--r--docs/docs/quickstart/studip-pdo.md86
-rw-r--r--docs/docs/quickstart/templates.md418
-rw-r--r--docs/docs/quickstart/trails.md253
-rw-r--r--docs/docs/quickstart/troubleshooting.md56
-rw-r--r--docs/docs/quickstart/ueberblick.md78
-rw-r--r--docs/docs/quickstart/url-helper.md117
-rw-r--r--docs/docs/quickstart/users.md189
-rw-r--r--docs/docs/quickstart/utf8.md48
-rw-r--r--docs/docs/quickstart/visual-style-guide.md1843
-rw-r--r--docs/docs/restapi/index.md3223
-rw-r--r--docs/docs/rules/fehler-melden.md104
-rw-r--r--docs/docs/rules/gitlab-workflows.md90
-rw-r--r--docs/docs/rules/introduction.md317
-rw-r--r--docs/docs/start.md5
-rw-r--r--docs/docs/testing/codeception.md238
-rw-r--r--docs/docs/testing/e2e.md54
-rw-r--r--docs/docs/thank-you.md17
-rw-r--r--docs/docs/visual-style-guide/css.md134
-rw-r--r--docs/docs/visual-style-guide/design.md108
-rw-r--r--docs/docs/visual-style-guide/dialoge.md74
-rw-r--r--docs/docs/visual-style-guide/einleitung.md84
-rw-r--r--docs/docs/visual-style-guide/elementlisten.md133
-rw-r--r--docs/docs/visual-style-guide/fonts.md14
-rw-r--r--docs/docs/visual-style-guide/formulare.md469
-rw-r--r--docs/docs/visual-style-guide/icons.md410
-rw-r--r--docs/docs/visual-style-guide/inhaltselemente.md121
-rw-r--r--docs/docs/visual-style-guide/logos.md30
-rw-r--r--docs/docs/visual-style-guide/navigation.md106
-rw-r--r--docs/docs/visual-style-guide/seitenaufbau.md56
-rw-r--r--docs/docs/visual-style-guide/sprache.md19
-rw-r--r--docs/docs/visual-style-guide/suche.md85
-rw-r--r--docs/docs/vuejs/start.md63
-rw-r--r--docs/docs/vuejs/vuex.md230
-rw-r--r--docs/docusaurus.config.js88
-rw-r--r--docs/i18n/de/code.json293
-rw-r--r--docs/i18n/de/docusaurus-plugin-content-blog/options.json14
-rw-r--r--docs/i18n/de/docusaurus-plugin-content-docs/current.json54
-rw-r--r--docs/i18n/de/docusaurus-theme-classic/footer.json34
-rw-r--r--docs/i18n/de/docusaurus-theme-classic/navbar.json26
-rw-r--r--docs/package.json38
-rw-r--r--docs/sidebars.js203
-rw-r--r--docs/src/css/custom.css53
-rw-r--r--docs/src/pages/index.js101
-rw-r--r--docs/src/pages/markdown-page.md7
-rw-r--r--docs/src/pages/styles.module.css37
-rw-r--r--docs/static/.nojekyll0
-rw-r--r--docs/static/img/docusaurus.pngbin0 -> 5142 bytes
-rw-r--r--docs/static/img/favicon.icobin0 -> 3626 bytes
-rw-r--r--docs/static/img/logo.svg1
-rw-r--r--docs/static/img/studip-hilfe.pngbin0 -> 27888 bytes
-rw-r--r--docs/static/img/undraw_docusaurus_mountain.svg170
-rw-r--r--docs/static/img/undraw_docusaurus_react.svg169
-rw-r--r--docs/static/img/undraw_docusaurus_tree.svg1
-rw-r--r--docs/static/pdf/entwicklerworkshop2013-activerecord.pdfbin0 -> 5396129 bytes
-rw-r--r--docs/static/pdf/entwicklerworkshop2014-attack_of_the_sorm.pdfbin0 -> 4174100 bytes
-rw-r--r--docs/static/pdf/entwicklerworkshop2015-sorm_sucks.pdfbin0 -> 3479470 bytes
-rw-r--r--docs/yarn.lock8208
200 files changed, 36322 insertions, 0 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0ae7375..1142197 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -32,6 +32,7 @@ stages:
- cache
- packaging
- release
+ - docs
.scripts:
mkdir-caches: &mkdir-caches
@@ -437,3 +438,16 @@ release:
- name: "studip-$CI_COMMIT_TAG.tar.gz"
url: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/jobs/${GE_JOB_ID}/artifacts/.pkg/studip-$CI_COMMIT_TAG.tar.gz"
link_type: package
+
+docs:
+ stage: deploy
+ interruptible: true
+ script:
+ - apk update && apk --no-cache add git
+ - cd docs
+ - yarn install
+ - yarn build
+ - mv ./build ../public
+ artifacts:
+ paths:
+ - public
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 0000000..b2d6de3
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,20 @@
+# Dependencies
+/node_modules
+
+# Production
+/build
+
+# Generated files
+.docusaurus
+.cache-loader
+
+# Misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/docs/.gitlab-ci.yml b/docs/.gitlab-ci.yml
new file mode 100644
index 0000000..9b9268e
--- /dev/null
+++ b/docs/.gitlab-ci.yml
@@ -0,0 +1,28 @@
+image: node:15.12-alpine3.13
+
+stages:
+ - test
+ - deploy
+
+test:
+ stage: test
+ script:
+ - cd website
+ - yarn install
+ - yarn build
+ except:
+ - master
+
+pages:
+ stage: deploy
+ interruptible: true
+ script:
+ - cd website
+ - yarn install
+ - yarn build
+ - mv ./build ../public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..ff2b117
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,33 @@
+# Website
+
+This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator.
+
+## Installation
+
+```console
+npm install
+```
+
+## Local Development
+
+```console
+npm start
+```
+
+This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
+
+## Build
+
+```console
+npm build
+```
+
+This command generates static content into the `build` directory and can be served using any static contents hosting service.
+
+## Deployment
+
+```console
+GIT_USER=<Your GitHub username> USE_SSH=true yarn deploy
+```
+
+If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
diff --git a/docs/babel.config.js b/docs/babel.config.js
new file mode 100644
index 0000000..e00595d
--- /dev/null
+++ b/docs/babel.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
+};
diff --git a/docs/docs/a11y/background.md b/docs/docs/a11y/background.md
new file mode 100644
index 0000000..f139be5
--- /dev/null
+++ b/docs/docs/a11y/background.md
@@ -0,0 +1,100 @@
+---
+title: Hintergründe
+slug: /a11y/background
+sidebar_label: Hintergründe
+---
+
+Seit Anfang der 1990er Jahre waren erste Bestrebungen, Dinge im Internet zu standardisieren, aufgekommen und seit 1994 vor allem durch das World Wide Web Consortium, besser bekannt als W3C-Konsortium, vorangetrieben worden, das technische Spezifikationen und Richtlinien erarbeitet und transparent macht. Standardisierte Technologien sind etwa HTML oder auch die Web Content Accessibility Guidelines, kurz WCAG.
+
+## WCAG 1.0
+Die zum W3C-Konsortium gehörende Web Accessibility Initiative (WAI) hat eine Empfehlungssammlung erstellt, die 1999 herausgegeben wurde. Diese enthält rund 60 Einzelpunkte in drei verschiedenen Prioritäten (A, AA, AAA) in 14 Kategorien, die eine Internetseite erfüllen soll. Die einzelnen Kategorien konzentrieren sich sehr auf die technischen Aspekte, insb. HTML und CSS, ansonsten bleiben die WCAG 1.0 aber recht unverbindlich, was die Umsetzung der Punkte und die spätere Validierung angeht.
+
+## WCAG 2.0
+Die WCAG 2.0 haben am 11. Dezember 2008 Empfehlungsstatus erhalten. Im Gegensatz zu den WCAG 1.0 konzentrieren sie sich nicht mehr auf HTML und CSS als wichtigste Standards des Internets, sondern beschreiben allgemeiner, wie Layouts, Interaktionen u. a. gestaltet sein müssen, damit das Angebot barrierefrei ist. Die Umsetzung dieser Richtlinien für die einzelnen Technologien wie HTML, Java, Flash oder PDF obliegt den jeweils verantwortlichen Institutionen bzw. Unternehmen. Damit bleiben die WCAG offen für die raschen technologischen Entwicklungen des Internets und neue Techniken lassen sich integrieren.
+Im Juni 2018 wurden die WCAG 2.1 als W3C Standard verabschiedet, die sich v.a. mit Sehbehinderungen und kognitive Beeinträchtigungen sowie mobiler Nutzung beschäftigen. [https://www.w3.org/TR/WCAG21/ https://www.w3.org/Translations/WCAG20-de/]
+
+Der WCAG-Test ist ein Testverfahren zur Prüfung der Zugänglichkeit von Webangeboten. Er wurde im Rahmen der vom Bundesministerium für Arbeit und Soziales geförderten Projektreihe „BIK – barrierefrei informieren und kommunizieren“ entwickelt und macht die Anforderungen der WCAG handhabbar, allerdings ist dieser Test in Bezug auf ein Lernmanagementsystem im Unterschied zu einer Webseite nicht sinnvoll.
+
+
+## Tests von Stud.IP auf Barrierefreiheit
+Aufgrund dieser Feststellung hat der Arbeitskreis Barrierefreiheit mithilfe des Stud.IP e.V. eine Überprüfung einer Stud.IP-4.6 und einer Stud.IP-5.0-Installation von einer u.a. auf Überprüfung von Lernmanagementsystemen spezialisierten Agentur durchführen lassen und anhand der ausführlichen Berichte einen Maßnahmenkatalog erstellt, um Stud.IP barriereärmer zu machen und zwar mit einem sog. BITV-Test.
+Geprüft wurde die 4.6er-Installation nach den 60 Prüfschritten, die sich aus 50 Anforderungen der WCAG 2.1 (Konformitäten A und AA) ergeben. Geprüft wurde nach den vier Prinzipien der WCAG 2.1. Diesen sind 13 Richtlinien zugeordnet, welche die Grundziele für die Erstellung barrierefreier Webinhalte bilden.
+
+
+- Wahrnehmbarkeit
+
+Dazu gehören z.B. ob es Text-Alternativen gibt, ob Dinge anpassbar und unterscheidbar sind.
+- Bedienbarkeit
+
+Es wird getestet, ob das System mit Tastatur bedienbar ist und wie gut die Navigierbarkeit und Eingabemodalitäten sind.
+- Verständlichkeit
+
+Hierbei spielt eine Rolle, ob Texte lesbar sind, die Bedienung intuitiv funktioniert und ob es z.B. Eingabehilfen gibt.
+- Robustheit
+
+WCAG-Prinzip: „Inhalte müssen robust genug sein, damit sie zuverlässig von einer großen Auswahl an Benutzeragenten einschließlich assistierender Techniken interpretiert werden können.“ Unter diesen Punkt fällt auch die Analyse auf korrekte Syntax.
+Die Prüfschritte haben sich 2021 auf 85 und 2022 sogar auf 98 erhöht.
+
+## Gesetzliche Vorgaben
+Auf die WCAG haben Staatenverbünde wie die EU, Staaten und Bundesländer reagiert und verschiedene Gesetze erlassen, die die Guidelines umsetzen sollen.
+Vorab sei zur besseren Einordnung bemerkt, dass jedes nationale Gesetz, das einer EU-Verordnung widerspricht, sofort wirkungslos ist. Denn eine europäische Verordnung wird mit ihrer Verkündung automatisch zum Bestandteil der nationalen Rechtsordnungen und muss nicht noch erst von den einzelnen EU-Mitgliedstaaten in nationales Recht umgesetzt werden.
+Eine europäische Richtlinie dagegen kommt erst nach einer im Gesetzestext festgelegten Frist zum Zuge. Vor dieser sich mitunter hinziehenden Verankerung in der bundesdeutschen Gesetzgebung sind die in einer europäischen Richtlinie festgelegten Ziele noch nicht für die hiesigen Behörden rechtlich verbindlich. Wobei hier die Form und die Mittel der Umsetzung dem jeweiligen Mitgliedsland sowieso selbst überlassen bleiben, so dass es in konkreten Fällen immer zu unterschiedlichen Gerichtsentscheidungen auf nationaler und europäischer Ebene kommen kann und wird.
+Die barrierefreie-Informationstechnik-Verordnung (BITV), die auf den WCAG 1.0 fußt und 2002 in der BRD erstmals in Kraft getreten ist, soll nach § 1 eine umfassend und grundsätzlich uneingeschränkt barrierefreie Gestaltung moderner Informations- und Kommunikationstechnik gewährleisten und gilt daher für
+
+- Webseiten
+- mobile Anwendungen (Apps)
+- elektronisch unterstützte Verwaltungsabläufe und
+- grafische Programmoberflächen
+des Bundes.
+
+Auch die Bundesländer der BRD haben darauf fußend entsprechende Gesetze erlassen, wie zum Beispiel Niedersachsen:
+Das NBGG, das Niedersächsische Behindertengleichstellungsgesetz (25.11.2007; Fassung vom 25.10.2018, gültig ab 02.11.2018) hat folgende wichtigste Inhalte:
+- (§ 9 a) öffentliche Stellen müssen ihre Webseiten und mobilen Anwendungen barrierefrei gestalten
+- (§ 9b) öffentliche Einrichtungen müssen eine Erklärung zur Barrierefreiheit veröffentlichen, die auch Stellen benennt, die ausnahmsweise nicht barrierefrei sind und Alternativen dazu
+- die einen Hinweis auf Durchsetzungsverfahren und Verlinkung zu Schlichtungsstellen enthält
+
+## Neue Gesetzeslage und Rahmenbedingungen
+Seit dem 26.10.2016 ist die EU-Richtlinie 2016/2012 des europäischen Parlaments und Rats über den barrierefreien Zugang zu den Websites und mobilen Anwendungen öffentlicher Stellen in Kraft, die sich an die Mitgliedstaaten richtet. Diese müssen die Richtlinie in nationales Recht überführen.
+Durch das Inkrafttreten der EU-Richtlinie und der sog. Harmonisierung in den Mitgliedstaaten haben die Diskussion und auch die Handlungen im Sinne der Barrierefreiheit sowohl auf Bundes- als auch auf Landesebene neue Fahrt aufgenommen.
+Die BITV 2.0 (in Kraft getreten am 25. Mai 2019) setzt die EU-Richtlinie 2016/2012 konkret um, die nicht schon 2018 im aktualisierten Behindertengleichstellungsgesetz (BGG) umgesetzt wurden. Die BITV 2.0 wurde seit 2007 in einer Arbeitsgruppe aus Vertretern von Behindertenverbänden, des Bundesministeriums für Arbeit und Soziales, des Bundesverwaltungsamtes sowie aus Forschung und Technik erarbeitet.
+Die neue Gesetzeslage hat veränderte Rahmenbedingungen zufolge: Es existieren seit einiger Zeit Überwachungsstellen für barrierefreies Internet; Beispiel Niedersachsen (nach § 9c NBGG)
+Dazu muss man sagen, dass die Politik zögerlich auf die Gesetzeslage reagiert hat. Außerdem sind einige der Überwachungsstellen tatsächlich schlecht im Netz zu finden.
+
+Die Überwachungsstellen haben folgende Aufgaben:
+- periodische Überwachung, ob und inwiefern die Angebote öffentlicher Stellen der Barrierefreiheit genügen
+- Feststellung von Mängelbeseitigung
+- Beratung, Schulung, Sensibilisierungsprogramme
+- Erstellen eines Berichtes (u.a. für den Landtag)
+- Unterstützung der sog. Schlichtungsstelle
+Die Schlichtungsstelle ist bei der oder dem Landesbeauftragten für Menschen mit Behinderungen eingerichtet. Diese ist für das sog. Durchsetzungsverfahren (nach Art. 9 der EU-Richtlinie 2016/2012) zuständig.
+Die Schlichtungsstelle muss:
+- unabhängig und unparteiisch handeln
+- Verfahrensregeln transparent handhaben/offenlegen
+- Beteiligten des Schlichtungsverfahrens rechtliches Gehör verschaffen
+- Vertraulichkeit gewährleisten
+- barrierefreie Kommunikation sicherstellen
+- und kann die Überprüfung einzelner öffentlicher Stellen durch die Überwachungsstelle verlangen
+
+## Was bedeutet das für Stud.IP und deren Betreiber:innen?
+Universitäten als Körperschaften öffentlichen Rechts und auch Verbände/Vereine gelten meist als öffentliche Einrichtungen, daher unterliegen sie der jeweiligen Bundes- oder Landesrechtsprechung und müssen daher entsprechende Anwendungen barrierefrei gestalten.
+Sie müssen über den Stand der Barrierefreiheit berichten und Feedbackprozesse etablieren, was auch Stud.IP und andere LMSe betrifft. Dazu gehören:
+1. eine Erklärung, die auf Webseiten eingebunden ist und Ansprechpartner benennt und über den Stand der Barrierefreiheit Auskunft gibt, (https://gitlab.studip.de/studip/studip/-/wikis/Barrierefreiheit#erkl%C3%A4rung-zur-barrierefreiheit-der-websiteapp-und-feedbackmechanismus)
+2. ein technischer Feedback-Mechanismus, der es erlaubt Rückmeldung über Barrieren zu geben, und zwar direkt an den Objekten bzw. auf der Seite, die Barrieren darstellen (https://gitlab.studip.de/studip/studip/-/wikis/Barrierefreiheit#erkl%C3%A4rung-zur-barrierefreiheit-der-websiteapp-und-feedbackmechanismus)
+3. Personenstellen, die sich um den Abbau von Barrieren kümmern. Die Durchsetzungsstellen haben in Bayern bei den HSen direkt nach dem Stand der Barrierefreiheit nachgefragt. Von vielen wird gewünscht, die LMSe zu zertifizieren, damit man dieses Zertifikat bei den Stellen einreichen kann. Die Software an sich kann man jedoch nicht zertifizieren, da man sie nicht getrennt vom Inhalt, den u.a. viele Lehrende einstellen, betrachten kann.
+Zu den ersten beiden Punkten ist bereits Abhilfe geschaffen worden. Die Möglichkeit, Barrieren von überall aus dem System melden zu können, ist bereits gegeben und seit 2021 als Plugin verfügbar (läuft ab Stud.IP 4.4). Die Inhalte und Kontaktdaten müssen vom jeweiligen Standort hinterlegt werden.
+
+Daraus folgt weiterhin, dass:
+- die öffentlichen Stellen befürchten müssen, dass sich Beschwerden häufen und Bugfixing/Verbesserungsbedarf an Plugins und dem Kern entstehen und somit ein erhöhter Zeit-/Personalbedarf entsteht
+- sie direkt von den Überwachungsstellen überwacht werden können
+- dass zu wenige Mitarbeiter:innen ins Thema eingearbeitet sind und Zeit für Einarbeitung/Weiterqualifikation einzuplanen ist
+- wenn es zu einem Durchsetzungs- oder sogar Schlichtungsverfahren kommt, die öffentliche Stelle verpflichtet ist, diese zu unterstützen.
+
+## Umsetzungsprojekt Stud.IP barriereärmer machen
+Der oben bereits erwähnte Maßnahmenkatalog wurde von sieben Hochschulen querfinanziert und einzelne Arbeitspakete in Auftrag gegeben. Bei data-quest ist darauf ein Umsetzungsprojekt entstanden, das von September 2021 bis April 2023 läuft. Ein Ergebnis ist die Dokumentation und Guidelines, wie man in Zukunft barriereärmer programmieren sollte.
+
+## Sicherstellung der Barrierearmut von neuem Programmcode
+Wie in Open Source-Projekten üblich, programmieren auch bei Stud.IP viele verschiedene Personen von verschiedensten Standorten an der Software und erweitern/modernisieren sie nicht nur durch Plugins, sondern auch durch neuen Code im Kern. Das stellt natürlich eine Herausforderung dar, wenn es darum geht, die Qualität der veröffentlichten Software zu gewährleisten und unter verschiedensten Kriterien zu überprüfen. Das Projekt hat jedoch einen merkbaren Einfluss auf das Bewusstsein und die Motivation der Entwickelnden zum Thema Barrierefreiheit genommen und so schon zu barriereärmeren Programmcode geführt. Die Community hat mit völliger Offenheit auf das Thema reagiert, neue Arbeitspakete werden nun immer auch von Anfang an unter dem Aspekt der Barrierefreiheit betrachtet.
+Das zentrale Gremium, das entscheidet, welche Arbeiten am Stud.IP-Code in neue Versionen aufgenommen werden und welchen Kriterien diese genügen müssen, ist die Coregroup. Hier wurde im Jahr 2022 eine neue Zuständigkeit zum Bereich „Barrierefreiheit“ geschaffen, die nicht nur als Anlaufstelle zu Fragen rund um barrierefreie Programmierung dient, sondern auch jeglichen Code, der in den Stud.IP-Kern eingebaut werden soll, auf barrierefreie Gestaltung und Bedienung überprüft und ggf. ein Veto gegen den Einbau einlegen darf. Damit wird sichergestellt, dass die im Umsetzungsprojekt vorgenommenen Änderungen nicht nur dokumentarisch erhalten bleiben, sondern der Programmcode von Stud.IP auch zukünftig nachhaltig unter dem Aspekt der Barrierefreiheit weiterentwickelt wird.
+
+## Barriereärmer programmieren
+Die Dokumentation, was bereits umgesetzt worden ist und worauf Programmierer:innen in Zukunft achten sollten, um barrierearmen Code zu erstellen, lesen Sie [hier](https://gitlab.studip.de/studip/studip/-/wikis/Barrierefreiheit#erkl%C3%A4rung-zur-barrierefreiheit-der-websiteapp-und-feedbackmechanismus).
diff --git a/docs/docs/a11y/declaration_template.md b/docs/docs/a11y/declaration_template.md
new file mode 100644
index 0000000..e1a1de2
--- /dev/null
+++ b/docs/docs/a11y/declaration_template.md
@@ -0,0 +1,135 @@
+---
+title: Mustertext zur Erklärung der Barrierefreiheit
+slug: /a11y/declaration_template
+sidebar_label: Mustertext
+---
+
+## Mustertext Erklärung zur Barrierefreiheit für Stud.IP
+
+[Text in eckigen Klammern ist ggf. zu ergänzen, zu streichen oder sprachlich anzupassen, je nachdem, wie das Ergebnis der Überprüfung der Barrierefreiheit ausfällt.]
+
+Diese Erklärung zur Barrierefreiheit gilt für die Stud.IP-Installation (5.x) unter der [URL der ergänzen; bitte Version und Datum angeben] der [Betreiber der Stud.IP-Installation ergänzen].
+
+Als öffentliche Stelle im Sinne der Richtlinie (EU) 2016/2102 sind wir bemüht, unsere Websites und mobilen Anwendungen im Einklang mit den Bestimmungen des Behindertengleichstellungsgesetzes des Bundes (BGG) sowie der Barrierefreien-Informationstechnik-Verordnung (BITV 2.0) zur Umsetzung der Richtlinie (EU) 2016/2102 barrierefrei zugänglich zu machen.
+
+[Hier ggf. jeweilige Landesverordnung zusätzlich einfügen, z.B. für Niedersachsen § 9 NBGG.]
+
+### Stand der Vereinbarkeit mit den Anforderungen
+
+Die Anforderungen der Barrierefreiheit ergeben sich aus §§ 3 Absätze 1 bis 4 und 4 der BITV 2.0, die auf der Grundlage von § 12d BGG erlassen wurde.
+
+Die Überprüfung der Einhaltung der Anforderungen beruht auf
+einer von Materna Information & Communications SE im Zeitraum von Ende Januar bis Anfang Februar 2021 vorgenommenen Bewertung. Maßstab der Prüfung ist die EN 301 549 und der A sowie AA Status der WCAG 2.1. Überprüft wurden die Vorgaben der WCAG 2.1 (Konformitätsstufen A und AA) anhand der 60 Prüfschritte des BITV/WCAG-Tests.
+
+Die Überprüfung bezieht sich auf das Stud.IP-Release 5.x. [Plugins und Inhalte müssen standortspezifisch geprüft und ggf. dokumentiert/diese Erklärung angepasst werden. Bitte ggf. ergänzen.]
+
+Diese Stud.IP-Installation ist nicht vollständig mit den für uns geltenden Vorschriften zur Barrierefreiheit vereinbar. Im Einzelnen:
+
+- Überschriftenhierarchien werden auf manchen Seiten nicht vollständig eingehalten.
+- Für Bilder, Bedienelemente und grafische Elemente sind in manchen Fällen keine, falsche oder unzureichende Alternativen vorhanden.
+- Grafiken und Bedienelementen fehlen in manchen Fällen korrekte Auszeichnungen, so dass sie von Assistenzsystemen nicht richtig erfasst werden können.
+- Die Sprache in Alternativtexten ist teilweise in Englisch angegeben ohne das der Sprachwechsel korrekt ausgezeichnet ist.
+- Listeneinträge, Tabellen(-spalten) und Formulare sind teilweise nicht korrekt ausgezeichnet.
+- Die sichtbare Reihenfolge von Seitenelementen weicht teilweise von der Reihenfolge im Quelltext ab.
+- Die Mindestanforderung an Kontraste ist nicht überall erfüllt.
+- Die Tastatursteuerung ist nicht uneingeschränkt benutzbar.
+- Auf der Startseite fehlt die Bereitstellung der Erläuterungen über die Website in Leichter Sprache und in Deutscher Gebärdensprache.
+
+Zudem können von Nutzerinnen und Nutzern eingestellte Inhalte, z.B. PDFs oder Videos, Barrieren aufweisen.
+
+Stud.IP wird derzeit in Bezug auf Barrierefreiheit überarbeitet. Folgende Maßnahmen zur Verringerung von Barrieren werden voraussichtlich in das Release von Stud.IP 5.1 und 5.2 einfließen:
+
+- Attribute und Alternativtexte werden hinzugefügt und korrigiert, damit Screen Reader Schaltflächen, Links, Bedienelemente und Grafiken korrekt interpretieren und passende Texte ausgeben können.
+- Die Hierarchien von Überschriften und Reihenfolge von Seitenelementen werden überarbeitet/korrigiert.
+- Sprachauszeichnungen werden vereinheitlicht.
+- Listeneinträge, Tabellen(-spalten) und Formulare werden korrekt ausgezeichnet und sinnvoll verknüpft.
+- Eine Möglichkeit den Kontrast zu verändern, wird implementiert.
+- Die Tastatursteuerung korrigiert.
+- Aktionsmenüs und Akkordeonelemente werden überarbeitet.
+- Die responsive Navigation wird verbessert.
+
+Folgende Inhalte sind aufgrund der Absicht, ein höheres Maß an digitaler Barrierefreiheit als gesetzlich gefordert umzusetzen, realisiert:
+[Geben Sie die jeweiligen Inhalte an]
+
+### Datum der Erstellung bzw. der letzten Aktualisierung der Erklärung
+
+Diese Erklärung wurde am [09/2021] erstellt und zuletzt am [Datum] aktualisiert.
+
+### Barrieren melden: Kontakt zu den Feedback Ansprechpartnern
+
+Sie möchten uns bestehende Barrieren mitteilen oder Informationen zur Umsetzung der Barrierefreiheit erfragen? Für Ihr Feedback sowie alle weiteren Informationen sprechen Sie unsere verantwortlichen Kontaktpersonen unter xxx an.
+
+[verlinkte URL mit Namen des Feedback-Mechanismus, z. B. „Barrieren melden“ angeben. Dabei sollte der Leitfaden „Erklärung zur Barrierefreiheit“ und der Leitfaden „Feedback-Mechanismus“ beachtet werden]
+
+### Schlichtungsverfahren
+
+[Nicht zutreffende Stelle streichen ggf. Stelle Ihres Bundeslandes einfügen]
+
+Wenn auch nach Ihrem Feedback an den oben genannten Kontakt keine zufriedenstellende Lösung gefunden wurde, können Sie sich an die Schlichtungsstelle nach § 16 BGG wenden. Die Schlichtungsstelle BGG hat die Aufgabe, bei Konflikten zum Thema Barrierefreiheit zwischen Menschen mit Behinderungen und öffentlichen Stellen des Bundes eine außergerichtliche Streitbeilegung zu unterstützen. Das Schlichtungsverfahren ist kostenlos. Es muss kein Rechtsbeistand eingeschaltet werden. Weitere Informationen zum Schlichtungsverfahren und den Möglichkeiten der Antragstellung erhalten Sie unter: [https://www.schlichtungsstelle-bgg.de](https://www.schlichtungsstelle-bgg.de).
+
+Direkt kontaktieren können Sie die Schlichtungsstelle BGG unter info@schlichtungsstelle-bgg.de.
+
+## Sample Text Accessibility Statement
+
+[Text in square brackets may need to be added, deleted, or linguistically adjusted depending on the outcome of the accessibility review].
+
+This accessibility statement applies to the Stud.IP installation (5.x) at the [add URL of; please specify version and date] of the [add operator of Stud.IP installation].
+
+As a public body within the meaning of Directive (EU) 2016/2102, we strive to make our websites and mobile applications accessible in accordance with the provisions of the Federal Disability Equality Act (BGG) and the Barrier-Free Information Technology Ordinance (BITV 2.0) implementing Directive (EU) 2016/2102.
+
+[Here, if necessary, insert the respective state ordinance additionally, e.g. for Lower Saxony § 9 NBGG].
+
+### Status of compatibility with the requirements
+
+The accessibility requirements result from §§ 3 paragraphs 1 to 4 and 4 of BITV 2.0, which was issued on the basis of § 12d BGG.
+
+The review of compliance with the requirements is based on
+an assessment performed by Materna Information & Communications SE in the period from the end of January to the beginning of February 2021. The benchmark for the test is EN 301 549 and the A and AA status of WCAG 2.1. The requirements of WCAG 2.1 (conformance levels A and AA) were checked using the 60 test steps of the BITV/WCAG test.
+
+The check refers to Stud.IP release 5.x. [Plugins and content must be checked site-specifically and documented/adapted to this statement if necessary. Please add if necessary].
+
+This Stud.IP installation is not fully compliant with the accessibility regulations that apply to us. In detail:
+
+- Heading hierarchies are not fully respected on some pages.
+- In some cases, there are no, incorrect or insufficient alternatives for images, control elements and graphical elements.
+- Graphics and control elements lack correct markup in some cases, so that they cannot be correctly detected by assistance systems.
+- The language in alternative texts is sometimes specified in English without the language change being correctly marked.
+- List entries, tables (columns) and forms are sometimes not correctly labeled.
+- The visible order of page elements sometimes differs from the order in the source text.
+- The minimum requirement for contrasts is not met everywhere.
+- The keyboard control is not fully usable.
+- The home page lacks the provision of explanations about the website in plain language and in German sign language.
+
+In addition, content posted by users, e.g. PDFs or videos, may have barriers.
+
+Stud.IP is currently being revised with regard to accessibility. The following measures to reduce barriers are expected to be included in the release of Stud.IP 5.1 and 5.2:
+
+- Attributes and alternative texts are added and corrected so that screen readers can correctly interpret buttons, links, controls and graphics and output appropriate texts.
+- Heading hierarchies and page element order are revised/corrected.
+- Language mark-ups will be standardized.
+- List entries, tables (columns) and forms will be labelled correctly and linked in a meaningful way.
+- A possibility to change the contrast is implemented.
+- The keyboard control is corrected.
+- Action menus and accordion elements are revised.
+- Responsive navigation will be improved.
+- The following content is implemented due to the intention to implement a higher level of digital accessibility than required by law:
+
+[Specify the respective content]
+
+### Date of preparation or last update of the declaration
+
+This statement was created on [09/2021] and last updated on [date].
+
+### Report Barriers: Contact Feedback Contacts
+
+Would you like to report existing barriers or request information on implementing accessibility? For your feedback as well as any further information, please contact our responsible contact persons at xxx.
+
+[provide linked URL with name of feedback mechanism, e.g. "Report barriers". The "Accessibility Statement" guide and the "Feedback Mechanism" guide should be followed].
+
+### Arbitration
+
+[Delete non-applicable body, if necessary insert body of your federal state].
+
+If a satisfactory solution has not been found even after you have sent feedback to the above-mentioned contact, you can turn to the conciliation body pursuant to Section 16 BGG. The BGG conciliation body is tasked with supporting out-of-court dispute resolution in the event of conflicts on the topic of accessibility between people with disabilities and federal public agencies. The conciliation procedure is free of charge. No legal counsel needs to be involved. For more information on the conciliation process and how to submit a request, visit: [https://www.schlichtungsstelle-bgg.de](https://www.schlichtungsstelle-bgg.de).
+
+You can contact the BGG conciliation body directly at info@schlichtungsstelle-bgg.de.
diff --git a/docs/docs/a11y/review.md b/docs/docs/a11y/review.md
new file mode 100644
index 0000000..8c36115
--- /dev/null
+++ b/docs/docs/a11y/review.md
@@ -0,0 +1,47 @@
+---
+title: Kriterien für Barrierefreiheits-Reviews
+slug: /a11y/review
+sidebar_label: Reviews
+---
+
+Generelle Informationen zur Barrierefreiheit sind auf der Seite [Barrierefreiheit](start) zu finden.
+
+Tests auf Barrierefreiheit sind in mehrere Bestandteile unterteilt:
+
+1. Prüfung auf ausreichenden Kontrast und Kenntlichmachung bei GUI-Elementen
+2. Prüfung auf Tastaturbedienbarkeit von Seitenelementen
+3. Prüfung auf Nutzbarkeit von Seitenelementen mit Screenreadern
+
+Die Fragestellungen der einzelnen Testschritte werden im folgenden aufgeführt.
+
+## Prüfung auf ausreichenden Kontrast und Kenntlichmachung bei GUI-Elementen
+
+- Haben Vordergrund- und Hintergrundfarbe einen ausreichenden Kontrast zueinander?
+- Werden Links passend hervorgehoben?
+- Sind die angezeigten Informationen auch ohne Farben erkennbar?
+
+## Prüfung auf Tastaturbedienbarkeit von Seitenelementen
+
+Lässt sich ein neuer oder geänderter Seitenbestandteil per Tastatur bedienen?
+
+- Kann ein Link, Button oder anderes interaktives Element per TAB angesprungen werden?
+- Können die üblichen Tasten zur Steuerung von interaktiven Elementen genutzt werden?
+ - Link, Button: Eingabetaste zum Aufrufen/Auflösen
+ - Checkbox, Radio-Button: Leertaste zum Auswählen/Abwählen
+ - Select-Box: Pfeiltasten zum Auswahl eines Eintrags
+
+## Prüfung auf Nutzbarkeit von Seitenelementen mit Screenreadern
+
+Wird ein neuer oder geänderter Seitenbestandteil korrekt für Screenreader ausgezeichnet?
+
+- Werden Formularelemente und Aktionselemente korrekt vorgelesen?
+ - Buttons als „Schalter“?
+ - Ein Button ändert einen Teil der Seite oder löst eine Aktion auf der aktuellen Seite aus. Beispiele: Aufklappen von Bereichen, Löschen oder Sortieren von Elementen in einer Liste.
+ - Links als „Link“?
+ - Ein Link ruft eine neue Seite im Hauptbereich oder im Dialog auf oder der Link ist ein Anker zu einer Position der aktuellen Seite.
+ - Select-Feld als „Auswahlfeld“?
+ - ...
+- Sind Icons, die nur Schmuckelemente sind, für Screenreader unsichtbar?
+- Sind Bilder oder Icons, die eine wichtige Information mitliefern, mit einem Alternativtext versehen?
+
+Getestet wird hauptsächlich mit der Kombination aus JAWS und Microsoft Edge. Beim Test mit anderen Screenreadern und Browsern sollten deren Marktanteile beachtet werden, damit eine getestete Lösung für möglichst viele Personen funktioniert: https://webaim.org/projects/screenreadersurvey9/
diff --git a/docs/docs/a11y/skiplinks.md b/docs/docs/a11y/skiplinks.md
new file mode 100644
index 0000000..d697ff1
--- /dev/null
+++ b/docs/docs/a11y/skiplinks.md
@@ -0,0 +1,106 @@
+---
+id: skiplinks
+title: Skiplinks
+sidebar_label: Skiplinks
+---
+
+Skiplinks ermöglichen für Tastaturbenutzer das schnelle Navigieren innerhalb einer Seite. In Stud.IP sind die Skiplinks zunächst verborgen. Erst durch das Drücken der Tab-Taste werden die Skiplinks eingeblendet. Dabei bekommt der erste Link der Liste den Focus, so dass durch weiteres Drücken der Tab-Taste durch die Liste navigiert werden kann. Durch Drücken der Eingabetaste wird das Ziel des Links angesprungen.
+
+Diese Klasse ermöglicht das Platzieren von Skiplinks an einer zentralen Stelle im Seitenlayout in Stud.IP. Diese Stelle besteht aus einem DIV-Container, der ziemlich direkt nach dem schließenden header-Tag ausgegeben wird. Grundsätzlich gibt jede Seite in Stud.IP, die ein Layout besitzt oder die `lib/include/html_head.inc.php` einbindet, diesen Container aus.
+
+Dieser Container enthält die eigentliche Liste mit Skiplinks als Aufzählungsliste. Jedes Listenelement besteht aus einem Skiplink.
+
+Die Klasse SkipLinks ermöglicht ein hinzufügen von Skiplinks zu dieser Liste zu jedem Zeitpunkt des Renderprozesses. Sie stellt auch Methoden zur Ausgabe der Liste bereit, die aber normalerweise nicht explizit aufgerufen werden müssen. Die Ausgabe erfolgt, wie bereits gesagt, an zentraler Stelle.
+
+## Methoden der Klasse
+
+Die Klasse SkipLinks kann nicht instanziiert werden, sie bietet ausschließlich Klassenmethoden, d.h. der Aufruf einer Methode erfolgt über `SkipLinks::`*Name*.
+
+* **addIndex($name, $id, $position = NULL, $overwritable = false)**
+
+ Registriert einen Link zu einem Ziel auf der ausgegbenen Seite (Anker).
+
+ `$name` ist der Name des Links, der in der Liste ausgegeben werden soll.
+
+ `$id` ist die id des HTML-Elements, das durch den Skiplink angesprungen werden soll. Dieses Element muss ein Attribut *id* mit dem hier übergebenen Wert besitzen. Die IDs müssen auf einer Seite natürlich eindeutig sein.
+
+ `$position` ist die Position des Skiplinks in der Liste. Dieser Parameter ermöglicht also ein Einsortieren der Links an eine bestimmte Position. Wird keine Position angegeben, wird der Link nach der ersten freien Position größer 100 angelegt.
+
+ Mit `$overwritable` kann festgelegt werden, ob ein Link an einer bestimmten Position von einem später im Renderprozess der Seite hinzugefügten Skiplink überschrieben werden kann.
+
+* **addLink($name, $url, $position = NULL, $overwritable = false)**
+
+ Registriert einen Link auf eine andere (auch externe) Seite.
+
+ Statt einer id für einen Anker wird durch den Parameter `$url` eine beliebige URL and die Methode übergeben, auf die der Link verweisen soll. Die anderen Parameter haben die gleiche Funktion wie bei addIndex().
+
+* **insertContainer()**
+
+ Fügt dem Pagelayout einen Container hinzu, der die Liste mit den Skiplinks aufnimmt.
+
+ Diese Funktion wird normalerweise beim Rendern einer Stud.IP-Seite automatisch an der richtigen Stelle Aufgerufen, so dass ein manuelles Aufrufen nicht erforderlich ist.
+
+* **getHTML()**
+
+ Gibt die registrierten Skiplinks formatiert anhand des Templates `templates/skiplinks` zurück. Das Template rendert die Skiplinks als HTML-Liste.
+
+ Auch diese Funktion wird beim Rendern einer Stud.IP-Seite automatisch aufgerufen, ein manuelles Aufrufen ist also auch hier nicht erforderlich.
+
+## Vorbelegte Positionen
+
+Eine Seite in Stud.IP verfügt in der Regel immer über einen Satz gleicher Inhaltsbereiche. Für diese werden automatisch Skiplinks mit einer festen Position registriert. Im einzelnen sind das:
+
+* Hauptnavigation im Header (Position 1, nicht überschreibbar)
+* Erste Reiternavigation (Position 10, nicht überschreibbar)
+* Zweite Reiternavigation (Position 20, nicht überschreibbar)
+* Hauptinhalt (Position 100, überschreibbar)
+* Infokasten (Position 10000, nicht überschreibbar)
+
+
+Durch diese vorbelegten Positionen ist es möglich, auch zwischen z.B. zweiter Reiternavigation und Hauptinhalt einen Skiplink zu setzen. Es sollte allerdings darauf geachtet werden, dass die Skiplinks zu den Haupnavigationsbereichen immer auf allen Seiten einheitlich am Anfang der Liste stehen, um den Benutzer nicht zu verwirren.
+
+## Beispiele
+
+Einfügen des Skiplinks für die Haupnavigation:
+
+```php
+[...]
+
+<div id="barTopFont">
+<?= htmlentities($GLOBALS['UNI_NAME_CLEAN']) ?>
+</div>
+<? SkipLinks::addIndex(_("Hauptnavigation"), 'barTopMenu', 1); ?>
+<ul id="barTopMenu" role="navigation">
+<? $accesskey = 0 ?>
+<? foreach (Navigation::getItem('/') as $nav) : ?>
+
+[...]
+```
+
+Verwendung von `addLink()` auf der `index_nobody.php`. Die Links der Startseite werden in die Skiplink-Liste übertragen, so dass der Benutzer z.B. direkt die Anmeldeseite aufrufen kann:
+
+```php
+[...]
+
+<? foreach (Navigation::getItem('/login') as $key => $nav) : ?>
+ <? if ($nav->isVisible()) : ?> <? list($name, $title) = explode(' - ', $nav->getTitle()) ?>
+ <div style="margin-left:70px; margin-top:10px; padding: 2px;">
+ <? if (is_internal_url($url = $nav->getURL())) : ?>
+ <a class="index" href="<?= URLHelper::getLink($url) ?>">
+ <? else : ?>
+ <a class="index" href="<?= htmlspecialchars($url) ?>" target="_blank">
+ <? endif ?>
+ <? SkipLinks::addLink($name, $url) ?>
+ <font size="4"><b><?= htmlReady($name) ?></b></font>
+ <font color="#555555" size="1"><br><?= htmlReady($title ? $title : $nav->getDescription()) ?></font>
+ </a>
+ </div>
+ <? endif ?>
+<? endforeach ?>
+
+[...]
+```
+
+## Ein wenig Magie
+
+Die Liste mit Skiplinks wird zunächst an das Ende der Seite angefügt. Per Javascript wird die Liste in den Container am Anfang der Seite verschoben. Um auch Benutzern älterer Screenreader die Navigation und das Erkennen von Inhaltsbereichen zu erleichtern wird per Javascript jedes Ziel eines Skiplinks mit dem Linktext als *h2*-Überschrift versehen. Das verlinkte HTML-Element wird mit dem Attribut `aria-labelledby` versehen, dass auf die Überschrift verweist, so dass der Inhaltsbereich entsprechend benannt ist.
diff --git a/docs/docs/a11y/start.md b/docs/docs/a11y/start.md
new file mode 100644
index 0000000..eb41aac
--- /dev/null
+++ b/docs/docs/a11y/start.md
@@ -0,0 +1,223 @@
+---
+title: Barrierefreiheit
+slug: /a11y/
+sidebar_label: Einleitung
+---
+
+Eine Zugänglichkeit des Lernmanagementsystems soll für ALLE Menschen erreicht werden. Daher geht man heute nicht mehr von dem im Grunde überholten Begriff behindertengerecht aus. Barrierearmut oder sogar -freiheit verbessert die Bedienbarkeit von Software für jede:n und ist damit inklusiv.
+Nicht nur Menschen mit permanenten motorischen, visuellen oder akustischen Einschränkungen sind betroffen. Es kann auch immer situative Einschränkungen geben, eine laute Umgebung, grelles Licht oder gleichzeitige Tätigkeiten beim Bedienen wie etwa telefonieren oder eine Verletzung. Daher nutzt uns allen eine Verbesserung von Webseiten und den webbasierten Lernmanagementsystemen wie Stud.IP. Behindert ist man nicht, behindert wird man.
+
+Eine ausführliche Einleitung, die auch auf die gesetzlichen Vorgaben und die Vorgehensweise in Projekt und Community eingeht,
+lesen Sie [hier](a11y/background.md).
+
+Um Barrieren in Stud.IP zu vermeiden, ist es notwendig, die folgenden Hinweise zur Barrierefreiheit zu beachten und bestimmte Konstrukte im HTML-Code, sowie in CSS-Regeln zu vermeiden.
+
+## Erklärung zur Barrierefreiheit der Website/App und Feedbackmechanismus
+
+Es muss eine separate Seite geben, auf der beschrieben wird, inwieweit das Webangebot bzw. die App barrierefrei ist und welche Bereiche (noch) nicht barrierefrei sind. Eine Mustererklärung zur Barrierefreiheit findet sich auf der folgenden Seite in der Dokumentation:
+[Mustertext zur Erklärung der Barrierefreiheit](a11y/declaration_template.md)
+
+Zusätzlich muss die Möglichkeit geschaffen werden, dass Nutzende die Seitenbetreiber auf Mängel in der Barrierefreiheit hinweisen. Beide Seiten müssen von jeder Seite aus erreichbar sein. In Stud.IP werden diese daher im Footer platziert. Beispiel:
+
+```php
+ $navigation = new Navigation(dgettext("accessibleform", "Barriere melden"));
+
+ $navigation->setURL(PluginEngine::getURL($this, [], 'form/index/'));
+
+ Navigation::addItem('/footer/form', $navigation);
+```
+
+Und so sieht der Footer dann in Stud.IP aus:
+
+![footer](../assets/6feeded7490d702b510a71a924934269/footer.png)
+
+## Farben und Kontraste
+
+Es gibt Richtlinien für Kontrastverhältnisse von, Text zu Hintergrund, Vordergrundfarbe zum Hintergrund und von Grafiken zum Hintergrund. Damit gemeint ist der Helligkeitsunterschied zwischen zwei benachbarten Farben. Dieser reicht vom schlechtesten Kontrast von 1:1, bei dem Farben identisch sind bis zum bestmöglichen Kontrast von 21:1 (schwarz auf weiß oder umgekehrt).
+
+### Anforderungen zum Kontrastverhältnis
+
+In den WCAG (Web Content Accessibility Guidelines) finden sich drei Punkte (Erfolgskriterien) zum Kontrastverhältnis:
+
+- **WCAG 1.4.3:** Mindestkontrast 4,5:1 bzw. 3:1 (großer Text)
+- **WCAG 1.4.6:** Erhöhter Kontrast 7:1 bzw. 4,5:1 (großer Text)
+- **WCAG 1.4.11:** Nicht-Text-Kontrast
+
+Diese Anforderungen gelten für Texte und Grafiken, die einen **Informationswert** besitzen. Reine **Schmuckbilder und -schriften** ohne für den Nutzenden relevante Information sind davon **ausgenommen.**
+
+### Textkontraste
+
+Generell gilt: Es wird zwischen kleineren und größeren Schriftgrößen unterschieden. Ein Mindestkontrastverhältnis von 4,5:1 ist für alle Schriftgrößen anzustreben. In Ausnahmefällen reicht ein Wert von 3:1; ideal ist jedoch ein Wert von 7:1.
+
+Für Schriftgrößen unter 24px oder 18pt (18,7px, beziehungsweise 14pt bei fetter Schrift) reicht ein Mindestkontrastverhältnis von 4,5:1.
+
+Für Schriftgrößen ab 24px oder 18pt reicht ein Mindestkontrastverhältnis von 3:1.
+
+#### Linkhervorhebungen
+
+Linkhervorhebungen müssen deutlich gekennzeichnet werden, sowohl mit einem ausreichend hohen Kontrast von (mindestens) 3:1 sowie durch andere Hervorhebungen, beispielsweise Unterstreichungen.
+
+Wird der Link fokussiert (CSS-Pseudoklassen :hover und :focus) muss der Kontrast noch verstärkt werden, beispielsweise durch eine andere Farbe. Auch hier ist auf ausreichenden Kontrast zum Hintergrund zu achten. Wenn dieser nicht ausreichend ist, muss eine Hervorhebung des fokussierten Links erzeugt werden.
+
+### Kontraste von Elementen, die kein Text sind
+
+Dieser Punkt betrifft grafische Elemente, wie zum Beispiel Icons, Diagramme oder Symbole.
+
+Wichtige Elemente des User Interface (Buttons, Icons) sowie grafisch dargestellte Informationen (Diagramme) dürfen Informationen nicht ausschließlich durch Farben vermitteln.
+
+Aktive Menüpunkte oder Icons, die einen aktiven Zustand kennzeichnen (zum Beipsiel Textformatierungswerkzeuge und Checkboxen), müssen von inaktiven Elementen klar unterscheidbar sein. Das Mindestkontrastverhältnis für alle Zustände beträgt 3:1.
+
+Von Farbänderungen ausgenommen sind beispielsweise Flaggen, Hitzekarten, Logos und andere grafische Elemente, bei der sich durch eine Farbänderung eine Änderung der Bedeutung ergeben würde.
+
+### Ausnahmen
+
+Von den Richtlinien ausgenommen sind folgende Elemente:
+
+- rein dekorativer Text, der keinen Informationswert besitzt
+- Logos oder Text in Logos
+- native UI-Komponenten
+- inaktive Elemente wie Schaltflächen, die mit „disabled“ gekennzeichnet sind
+- der browsereigene Fokus-Indikator, sofern er die Kontrastvoraussetzungen erfüllt
+
+## Tastaturbedienbarkeit
+
+Tastaturbedienbarkeit bedeutet, dass alle Elemente einer Seite auch per Tastatur erreichbar sind und ohne die Bedienung der Maus oder anderer Eingabeelemente genutzt werden können.
+
+### Steuerung über die Tastatur
+
+Eine sehr grundlegende Steuerung per Tastatur kann über die Tabulator-Taste erreicht werden. Damit kann durch alle fokussierbaren Elemente navigiert werden. Durch die Tastenkombination der Tabulatortaste mit der Umschalttaste navigiert man rückwärts durch die fokussierbaren Elemente.
+
+Bei auswählbaren Elemente wie Checkboxen und Radio-Buttons können die Pfeiltasten zur Navigation durch die Einträge verwendet werden.
+
+Durch Betätigen der Leertaste lassen sich Checkboxen und Radio-Buttons aktivieren oder deaktivieren. Select-Felder lassen sich damit auch öffnen.
+
+Die Eingabetaste dient zum Folgen von Links, Betätigen von Buttons, der Auswahl eines Eintrags eines Select-Feldes und zum Absenden von Formularen.
+
+Screenreader bieten zusätzliche Tastenkombinationen und Tastenbelegungen an, mit denen schnell zu Textabsätzen, Tabellen, Regionen der Seite oder Links navigiert werden kann. Diese sind jedoch je nach verwendetem Screenreader unterschiedlich.
+
+### Zurücksetzen der Fokussierung vermeiden
+
+Es muss sichergestellt sein, dass ein Zurücksetzen der Fokussierung beim Öffnen oder Schließen von Dialogen oder anderen Elementen vermieden wird. Ein Zurücksetzen führt dazu, dass wieder das erste fokussierbare Element der Seite im Fokus ist statt dem Element, mit dem der Dialog geöffnet wurde. Dadurch kann der Weg durch alle fokussierbaren Elemente zurück zum Inhalt der Seite, auf dem man zuvor war, sehr lange und beschwerlich sein. Auch ein Sprung hinter das letzte fokussierte Element muss vermieden werden, da sonst als nächstes das erste fokussierte Element der grafischen Oberfläche des Browsers im Fokus steht und der Weg zurück zum Seiteninhalt nochmal länger ist.
+
+### Skiplinks
+
+Skiplinks erleichern die Bedienung einer Seite, da man dank ihnen direkt zum interessanten Bereich der Seite springen kann und nicht gezwungen ist, alle Elemente einer Seite in der üblichen Reihenfolge durchzugehen, bis man das richtige Element gefunden hat.
+
+In Stud.IP hilft die SkipLink-Klasse bei der Bereitstellung dieser Funktionalität. Es lassen sich damit bestimmte Seitenelemente anspringen und fokussieren. Wird ein Skiplink hinzugefügt, kann dessen Position ebenfalls festgelegt werden.
+
+#### Standardmäßig vorhandenen Skiplinks und deren Positionen
+
+Folgende Skiplinks sind standardmäßig im Stud.IP aktiviert:
+
+| Name | Element-ID | Position\*\* |
+|------|------------|--------------|
+| Profilmenü | header_avatar_image_link | 1 |
+| Hauptnavigation | barTopMenu | 2 |
+| Zweite Navigationsebene\* | tabs | 10 |
+| Dritte Navigationsebene\*\* | nav_layer_3 | 20 |
+| Aktionen\*\* | sidebar_actions | 21 |
+| Hauptinhalt | layout_content | 100 |
+| Fußzeile\*\* | layout_footer | 900 |
+| Suche\*\* | globalsearch-input | 910 |
+| Tipps & Hilfe\*\* | helpbar_icon | 920 |
+
+\*= bis Stud.IP 5.0: Erste Reiternavigation
+
+\*\*=erst ab Stud.IP 5.1
+
+#### Skiplink hinzufügen
+
+Es können z.B. in Plugins zusätzliche Skiplinks hinzugefügt werden, wenn diese auf ein Element verweisen, das schnell erreichbar sein soll. Dazu wird die SkipLink-Klasse folgendermaßen aufgerufen:
+
+```php
+//Füge den Skiplink mit der Beschriftung „Neuer Skiplink“ hinzu, der auf
+//das Element mit der ID „id_zum_element“ verweist. Der Skiplink soll an
+//die Position 200 gesetzt werden und nicht von anderen Codestellen
+//überschreibbar sein (false):
+SkipLinks::addIndex('Neuer Skiplink', 'id_zum_element', 200, false);
+```
+
+Neue Skiplinks sollten nur sparsam hinzugefügt werden und nicht zwischen den Skiplinks des Kernsystems platziert werden, damit das „Muskelgedächtnis“ für die Standard-Skiplinks bei der Bedienung mit der Tastatur funktionieren kann.
+
+## Verwendung von ARIA-Rollen und -Landmarks
+
+Nicht alle ARIA-Rollen können sinnvoll in Stud.IP verwendet werden. Andere können zwar im Prinzip verwendet werden, aber besser wäre in dem Fall, das zugrundeliegende HTML zu ändern.
+
+### „menu“ und „menuitem“ nicht verwenden
+
+Die Rollen „menu“ und „menuitem“ sollten in Stud.IP nicht verwendet werden. Hintergrund ist, dass „menu“ ein Menü beschreibt, das genauso bedienbar sein soll, wie das Menü einer Desktop-Anwendung: Pfeiltasten statt Tab oder Umschalt-Tab. Bei einem HTML-Element mit der Rolle „menu“ sagen Screenreader wie JAWS, das Element sei mit Pfeiltasten bedienbar, was im Stud.IP-Kontext bei keinem Menü der Fall ist.
+
+Mehr Informationen zur Problematik der „menu“ und „menuitem“-Rollen findet sich hier: https://adrianroselli.com/2017/10/dont-use-aria-menu-roles-for-site-nav.html
+
+
+## Testen mit Screenreadern
+
+Um zu testen, dass eine Entwicklung auch für Blinde oder Personen mit sehr eingeschränkter Sicht nutzbar ist, können Screenreader genutzt werden. Mit ihnen wird der Inhalt einer Seite vorgelesen. Über besondere Tastenkombinationen können bestimmte Elemente einer Seite direkt angesprungen werden, um eine langwierige Navigation über die Tabulator-Taste zu vermeiden.
+
+## Welche Screenreader gibt es?
+
+* Für Windows: JAWS oder NVDA
+* Für Mac OS X und iOS: VoiceOver
+* Für GNU/Linux: Orca
+* Für Android: TalkBack in Kombination mit eSpeak
+
+## Kombinationen von Screenreadern und Browsern
+
+Nicht alle Screenreader funktionieren mit allen Browsern gut. Folgende Kombinationen haben sich bei Tests als „harmonierend“ erwiesen:
+
+* JAWS mit Microsoft Edge
+* Orca mit Chromium
+
+## Barrierefreiheit in Stud.IP
+
+### Dateimarker für barrierefreie Dateien und dessen Konfiguration
+
+Ab Stud.IP 5.3 gibt es die Möglichkeit, eine Datei beim Upload als "barrierefrei" zu markieren. Dazu wurde der bestehende Dialog umgebaut und um einen Barrierefreiheits-Bereich erweitert. Dort befindet sich eine Checkbox, mit der man bestätigen kann, dass die eben hochgeladene Datei barrierefrei ist.
+
+![dialog](../assets/a0377c72d0325b7d57151aca9ad52143/dialog.png)
+
+Ist eine Datei barrierefrei, so taucht in der Dateienliste ein Barrierefreiheitssymbol hinter dem Dateinamen auf.
+
+![dateiliste](../assets/69756a9f57c09cc3f3450519af957b23/dateiliste.png)
+
+Der Infotext **unter** der Checkbox lässt sich im Adminbereich auf Wunsch anpassen, beispielsweise um Verlinkungen oder Mailadressen von Ansprechpartner hinsichtlich Barrierefreiheit.
+
+![konfiguration](../assets/bbe72ca0392b9f0f40989e5fabba6e14/konfiguration.png)
+
+Hinweis: Eine Datei kann jederzeit nachträglich als barrierefrei/nicht barrierefrei gekennzeichnet werden.
+
+### Neue HTML-Struktur (ab Stud.IP 5.3)
+
+Um den Aufbau von Seiten in Stud.IP verständlicher und kompatibler für Screenreader zu machen, wurde für Version 5.3 die gesamte HTML-Struktur überarbeitet. Dokumentation hierzu (inklusive einer Übersicht aller geänderten Elemente) findet sich [in diesem Artikel](https://gitlab.studip.de/studip/studip/-/wikis/Neue-HTML-Struktur-ab-Stud.IP-5.3).
+
+### Drag & Drop
+
+Generell ist die Benutzung von Drag & Drop mit Herausforderungen verbunden, wenn die Lösung barrierearm
+sein soll. Nicht nur braucht es JavaScript-Code, der auf Pfeiltasten-Events reagiert und Elemente
+entsprechend positioniert. Die Bereiche der Seite, in denen die Drag & Drop Steuerung genutzt
+werden soll, müssen auch die aria-Rolle „application“ erhalten, damit Screenreader wissen, dass
+in diesen Bereichen keine speziellen Regeln für bestimmte HTML-Elemente angewendet werden sollen.
+
+Eine genauere Beschreibung der aria-Rolle „application“ und deren Auswirkungen sind bei MDN
+zu finden: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/application_role
+
+## Weiterführende Links
+
+Die Seite [Tipps und Tricks](a11y/tips.md) zeigt Lösungen für konkrete Probleme auf,
+die beim barrierearmen Programmieren auftauchen können.
+
+### Farben und Kontraste
+
+- Kontrastrechner zur Berechnung und Überprüfung barrierefreier Farbkombinationen: https://www.leserlich.info/werkzeuge/kontrastrechner/
+
+### ARIA-Rollen
+
+- https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques
+
+### Skiplinks
+
+- https://www.w3schools.com/accessibility/accessibility_skip_links.php
+
+### FAQ der Bundesfachstelle Barrierefreiheit
+
+- https://www.bundesfachstelle-barrierefreiheit.de/DE/Fachwissen/Informationstechnik/EU-Webseitenrichtlinie/FAQ/fragen-antworten-eu-richtlinie-websites-und-mobile-anwendungen.html
diff --git a/docs/docs/a11y/tips.md b/docs/docs/a11y/tips.md
new file mode 100644
index 0000000..73380db
--- /dev/null
+++ b/docs/docs/a11y/tips.md
@@ -0,0 +1,181 @@
+---
+title: Tipps & Tricks
+slug: /a11y/tips
+sidebar_label: Tipps & Tricks
+---
+
+Im Folgenden werden mit Codeschnipseln Lösungen für Probleme gezeigt, die bei der
+Programmierung möglichst barriererarmer Lösungen in Stud.IP auftreten können.
+
+### Elemente tastaturbedienbar machen
+
+HTML-Elemente, die standardmäßig nicht tastaturbedienbar sind, können über das Attribut „tabindex“ tastaturbedienbar gemacht werden. Hier als Beispiel für ein LABEL-Element:
+
+```html
+<!-- Positivbeispiel: So sollte es gemacht werden. -->
+<label for="some-element" tabindex="0">
+....
+</label>
+```
+
+Das „tabindex“-Attribut mit dem Wert 0 fügt das LABEL-Element in die Liste mit fokussierbaren Elementen ein.
+
+Der Wert für das „tabindex“-Attribut soll auf 0 gesetzt sein, wenn ein standardmäßig nicht fokussierbares Element fokussierbar gemacht werden soll. Für den umgekehrten Fall, bei dem ein fokussierbares Element nicht fokussierbar gemacht werden soll, beträgt der Wert des „tabindex“-Attributes -1.
+
+Andere Werte als 0 und -1 sollten für tabindex nicht verwenden werden, weil sie in die „natürliche“ Reihenfolge der fokussierbaren Elemente eingreifen und zu unerwarteten Sprüngen führen können, wenn zwei Elemente in unterschiedlichen Seitenbereichen eine feste Nummer im Tabindex bekommen haben. Beispiel:
+
+```html
+<!-- Negativbeispiel. So sollte es nicht gemacht werden. -->
+<main>
+ <div tabindex="2">E1</div>
+ ...
+ <a href=".../e2.php" tabindex="4">E2</a>
+</main>
+<footer>
+ <a href=".../f1.php" tabindex="1">F1</a>
+ <button tabindex="3">F2</button>
+</footer>
+```
+
+Die Reihenfolge der fokussierbaren Elemente wäre hier: F1, E1, F2, E2 statt der „natürlichen“ Reihenfolge E1, E2, F1, F2.
+
+### „click“-Event auch beim Drücken der Eingabetaste auslösen
+
+Seit der Behebung von [BIESt 106](https://gitlab.studip.de/studip/studip/-/issues/106 "Wiki: Inhaltsverzeichnis nicht per Tastaturnavigation erreichbar") gibt es ein Stück generellen JavaScript-Code, der bei allen Elementen aktiv wird, die der Klasse „enter-accessible“ angehören. Wird solch ein Element fokussiert und die Eingabetaste gedrückt, wird das click-Event ausgelöst, das normalerweise nur bei Mausklicks ausgelöst wird. Das LABEL-Element aus dem Beispiel von oben müsste so erweitert werden:
+
+```html
+<label for="some-element" class="enter-accessible" tabindex="0">
+....
+</label>
+```
+
+### „button“-Rolle vermeiden, stattdessen BUTTON-Elemente nehmen
+
+Mit der ARIA-Rolle „button“ können Elemente als „Schalter“ (button) ausgezeichnet werden, die das normalerweise nicht sind. So können zum Beispiel Links, die eigentlich eine Aktion ausführen statt auf eine andere Seite zu verweisen, mit der „button“-Rolle ausgezeichnet werden, damit sie von Screenreadern als Schalter statt als Verknüpfung vorgelesen werden:
+
+```html
+<a href="#" aria-role="button">
+ Hinzufügen
+</a>
+```
+
+Das Problem in diesem Beispiel ist, dass das A-Element für Anker an der Stelle schon unpassend ist, da es sich nicht um einen Anker oder Link handelt. Die bessere Lösung besteht darin, im HTML direkt ein BUTTON-Element zu verwenden:
+
+```html
+<button type="button">Hinzufügen</button>
+```
+
+### Checkboxen und Radio-Buttons verstecken
+
+Wenn man Checkboxen bzw. Radio-Buttons verstecken möchte, sollte man das nicht mit display:none oder visibility:hidden tun, denn dann sind die dazugehörigen Inhalte nicht mehr mit der Tastatur fokussierbar.
+
+Eine browserübergreifende Lösung ist Folgendes:
+
+```css
+position: fixed;
+opacity: 0;
+pointer-events: none;
+```
+
+Es ist auch nicht ratsam, anstelle der regulären Checkboxen eigene Grafiken zu verwenden.
+
+
+### input-Elemente: minlength-Attribut wird von Screenreadern nicht beachtet
+
+Wenn input-Elemente mit einem minlength-Attribut ausgestattet sind, wird dieses von Screenreadern nicht beachtet. Deswegen muss mit anderen Mitteln darauf hingewiesen werden, dass eine Mindestmenge von Zeichen eingegeben werden muss.
+
+Im Falle, dass zu wenig Zeichen eingegeben wurden, sollte auch ein barrierefreier Hinweis erfolgen, der auf dieses Problem hinweist. Dies kann mit einer aria-live Region und JavaScript gemacht werden, das beim Verlassen
+des Feldes die Länge des eingegebenen Textes prüft und dann eine Meldung über die aria-live Region ausgibt.
+
+Die Prüfung auf die Einhaltung des maxlength-Attributes kann auf die gleiche Art und Weise erfolgen.
+
+### Barrierearme Formulare programmieren
+
+Wenn möglich, sollte man den Formularbaukasten nutzen, der hier dokumentiert ist: https://gitlab.studip.de/studip/studip/-/wikis/StudipForm#die-form-klasse-ab-52
+
+Mit diesem Baukasten ist das Bauen eines Formulars vielleicht etwas ungewohnt; das Ergebnis wird aber stets barrierearm sein, weil die Standardelemente dieses Baukastens barrierearm und gut zu bedienen sind.
+
+### Akkordeonelemente barrierearm programmieren
+
+Akkordeonelemente, wie die Auswahl der Nutzungsbedingungen im Dateibereich, sollten so programmiert werden,
+dass sie auch per Tastatur genutzt werden können und ihr Inhalt von Screenreadern vorgelesen werden kann.
+Üblicherweise haben solche Elemente Radio-Buttons, die durch eine Grafik ersetzt werden, um dem Stud.IP-Design
+zu entsprechen. Wenn die Radio-Buttons aus dem Grund versteckt werden sollen, darf dies nicht durch die CSS-Angabe
+„display: none“ geschehen, weil das Element dann nicht per Tastatur erreichbar ist. Stattdessen sollte „opacity: 0“
+verwendet werden. Damit ist solch ein Radio-Button noch per Tastatur erreichbar, aber trotzdem unsichtbar.
+
+Das Beispiel aus dem Stud.IP-Dateibereich sieht für einen Eintrag der Nutzungsbedingungen nach dem
+Rendern folgendermaßen im Quellcode aus:
+
+```html
+<input type="radio" name="content_terms_of_use_id" value="SELFMADE_NONPUB" id="content_terms_of_use-SELFMADE_NONPUB"
+ checked="" aria-description="(Beschreibung der Nutzungsbedingungen)">
+<label for="content_terms_of_use-SELFMADE_NONPUB">
+ <div class="icon">
+ <img src="(Icon der Nutzungsbedingung)" alt="" class="icon-role-clickable icon-shape-own-license" width="32" height="32">
+ </div>
+ <div class="text">(Name der Nutzungsbedingung)</div>
+ <img class="arrow icon-role-clickable icon-shape-arr_1down" src="(Aufklapp-Icon)" alt="" width="24" height="24">
+ <img class="check icon-role-clickable icon-shape-check-circle" src="(Ausgewählt-Icon)" alt="" width="32" height="32">
+</label>
+<div class="terms_of_use_description">
+ <div class="description">
+ <div class="formatted-content">(Beschreibung der Nutzungsbedingung)</div>
+ </div>
+</div>
+```
+
+Wird ein Radio-Button ausgewählt, ist kein zusätzliches JavaScript notwendig, um die Lösung barriereärmer zu machen.
+Dadurch, dass der Radio-Button die gesamte Beschreibung der Nutzungsbedingungen enthält, muss nicht auf aria-live
+Regionen zurückgegriffen werden, um den Text vorlesen zu lassen.
+
+
+### Barriereärmeres Drag & Drop zur Sortierung von Elementen
+
+Soll eine Sortierung per Drag & Drop nicht nur in der GUI, sondern auch per Tastatur unter Nutzung eines Screenreaders möglich sein,
+so kann mit relativ wenig Aufwand dafür gesorgt werden, dass die Sortierung barriereärmer wird.
+Für eine Tabelle mit sortierbaren Zeilen sieht ein Teil der Lösung unter Nutzung von vue.js folgendermaßen aus:
+````vue
+<span aria-live="assertive" class="sr-only">{{ assistiveLive }}</span>
+
+
+<draggable v-model="elements" handle=".drag-handle" :animation="300" @end="dropElement" tag="tbody" role="listbox">
+ <tr v-for="(element, index) in elements" :key="index">
+ <td>
+ <a v-if="elements.length > 1" class="drag-link" role="option" tabindex="0" :title="$gettextInterpolate($gettext('Sortierelement für Element %{node}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.'), {node: element.name})" @keydown="keyHandler($event, index)" :ref="'draghandle-' + index">
+ <span class="drag-handle"></span>
+ </a>
+ </td>
+ ...
+ </tr>
+</draggable>
+````
+Die entscheidenden Dinge sind hier:
+
+- `role="listbox"` am Container und `role="option"` an den einzelnen Elementen. Ohne diese Rollenzuweisung funktionieren die Pfeiltasten nur innerhalb von Formularen, weil sonst der Screenreader die Pfeiltastenaktion abfängt.
+- `@keydown="keyHandler($event, index)"` Die Methode reagiert auf Drücken einer Taste (innerhalb der Methode wird weiter darauf geprüft, ob es sich um "Pfeil nach oben" (`keyCode 38`) oder "Pfeil nach unten" (`keyCode 40`) handelt), löst die Neusortierung aus und setzt einen entsprechenden Hinweistext über `assistiveLive`. Dabei sollte auch vorgelesen werden, wie die neue Position des Elements in der List ist, à la "Element ist an Position X von Y".
+
+````vue
+switch (e.keyCode) {
+ case 38: // up
+ e.preventDefault();
+ this.decreasePosition(index);
+ this.$nextTick(() => {
+ this.$refs['draghandle-' + (index - 1)][0].focus();
+ this.assistiveLive = this.$gettextInterpolate(
+ this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
+ { pos: index, listLength: this.children.length }
+ );
+ });
+ break;
+ ...
+````
+
+An der passenden Stelle muss der umschließende Bereich, in dem eine Sortierung möglich sein soll,
+mit der ARIA-Rolle „application“ versehen werden, damit Screenreader wissen, dass dieser Bereich
+eine eigene Tastatursteuerung hat, die vom Screenreader nicht verändert werden soll. Ansonsten kann
+es vorkommen, dass die Eventbehandler für die Pfeiltasten nicht ausgeführt werden, weil der
+Screenreader mit den Pfeiltasten-Events bereits etwas anderes macht, wie zum Beispiel auf das
+nächste Element mit Text zu springen, um dieses vorzulesen.
+
+Quelle: https://medium.com/salesforce-ux/4-major-patterns-for-accessible-drag-and-drop-1d43f64ebf09
diff --git a/docs/docs/a11y/todo.md b/docs/docs/a11y/todo.md
new file mode 100644
index 0000000..645f2e8
--- /dev/null
+++ b/docs/docs/a11y/todo.md
@@ -0,0 +1,285 @@
+---
+title: Barrierefreiheit
+sidebar_label: TODO Abgleichen
+---
+
+Um Barrieren in Stud.IP zu vermeiden, ist es notwendig, die folgenden Hinweise
+zur Barrierefreiheit zu beachten und bestimmte Konstrukte im HTML-Code,
+sowie in CSS-Regeln zu vermeiden.
+
+
+## Farben und Kontraste
+
+Es gibt Richtlinien für Kontrastverhältnisse von, Text zu Hintergrund,
+Vordergrundfarbe zum Hintergrund und von Grafiken zum Hintergrund. Damit gemeint
+ist der Helligkeitsunterschied zwischen zwei benachbarten Farben. Dieser reicht
+vom schlechtesten Kontrast von 1:1, bei dem Farben identisch sind bis zum
+bestmöglichen Kontrast von 21:1 (schwarz auf weiß oder umgekehrt).
+
+### Anforderungen zum Kontrastverhältnis
+
+In den WCAG (Web Content Accessibility Guidelines) finden sich drei
+Punkte (Erfolgskriterien) zum Kontrastverhältnis:
+
+- **WCAG 1.4.3:** Mindestkontrast 4,5:1 bzw. 3:1 (großer Text)
+- **WCAG 1.4.6:** Erhöhter Kontrast 7:1 bzw. 4,5:1 (großer Text)
+- **WCAG 1.4.11:** Nicht-Text-Kontrast
+
+Diese Anforderungen gelten für Texte und Grafiken, die einen
+**Informationswert** besitzen. Reine **Schmuckbilder und -schriften**
+ohne für den Nutzenden relevante Information sind davon **ausgenommen.**
+
+### Textkontraste
+
+Generell gilt: Es wird zwischen kleineren und größeren Schriftgrößen
+unterschieden. Ein Mindestkontrastverhältnis von 4,5:1 ist für alle
+Schriftgrößen anzustreben. In Ausnahmefällen reicht ein Wert von 3:1; ideal ist
+jedoch ein Wert von 7:1.
+
+Für Schriftgrößen unter 24px oder 18pt (18,7px, beziehungsweise 14pt bei fetter
+Schrift) reicht ein Mindestkontrastverhältnis von 4,5:1.
+
+Für Schriftgrößen ab 24px oder 18pt reicht ein Mindestkontrastverhältnis
+von 3:1.
+
+#### Linkhervorhebungen
+
+Linkhervorhebungen müssen deutlich gekennzeichnet werden, sowohl mit einem
+ausreichend hohen Kontrast von (mindestens) 3:1 sowie durch andere
+Hervorhebungen, beispielsweise Unterstreichungen.
+
+Wird der Link fokussiert (CSS-Pseudoklassen :hover und :focus) muss der Kontrast
+noch verstärkt werden, beispielsweise durch eine andere Farbe. Auch hier ist auf
+ausreichenden Kontrast zum Hintergrund zu achten. Wenn dieser nicht ausreichend
+ist, muss eine Hervorhebung des fokussierten Links erzeugt werden.
+
+### Kontraste von Elementen, die kein Text sind
+
+Dieser Punkt betrifft grafische Elemente, wie zum Beispiel Icons, Diagramme
+oder Symbole.
+
+Wichtige Elemente des User Interface (Buttons, Icons) sowie grafisch
+dargestellte Informationen (Diagramme) dürfen Informationen nicht
+ausschließlich durch Farben vermitteln.
+
+Aktive Menüpunkte oder Icons, die einen aktiven Zustand kennzeichnen
+(zum Beipsiel Textformatierungswerkzeuge und Checkboxen), müssen von inaktiven
+Elementen klar unterscheidbar sein. Das Mindestkontrastverhältnis für alle
+Zustände beträgt 3:1.
+
+Von Farbänderungen ausgenommen sind beispielsweise Flaggen, Hitzekarten, Logos
+und andere grafische Elemente, bei der sich durch eine Farbänderung eine
+Änderung der Bedeutung ergeben würde.
+
+
+### Ausnahmen
+
+Von den Richtlinien ausgenommen sind folgende Elemente:
+
+- rein dekorativer Text, der keinen Informationswert besitzt
+- Logos oder Text in Logos
+- native UI-Komponenten
+- inaktive Elemente wie Schaltflächen, die mit „disabled“ gekennzeichnet sind
+- der browsereigene Fokus-Indikator, sofern er die Kontrastvoraussetzungen
+ erfüllt
+
+
+## Tastaturbedienbarkeit
+
+Tastaturbedienbarkeit bedeutet, dass alle Elemente einer Seite auch per Tastatur
+erreichbar sind und ohne die Bedienung der Maus oder anderer Eingabeelemente
+genutzt werden können.
+
+### Steuerung über die Tastatur
+
+Eine sehr grundlegende Steuerung per Tastatur kann über die Tabulator-Taste
+erreicht werden. Damit können durch alle fokussierbaren Elemente navigiert
+werden. Durch die Tastenkombination der Tabulatortaste mit der Umschalttaste
+navigiert man rückwärts durch die fokussierbaren Elemente.
+
+Bei auswählbaren Elemente wie Checkboxen und Radio-Buttons können die
+Pfeiltasten zur Navigation durch die Einträge verwendet werden.
+
+Durch Betätigen der Leertaste lassen sich Checkboxen und Radio-Buttons
+aktivieren oder deaktivieren. Select-Felder lassen sich damit auch öffnen.
+
+Die Eingabetaste dient zum Folgen von Links, Betätigen von Buttons,
+der Auswahl eines Eintrags eines Select-Feldes und zum Absenden von Formularen.
+
+Screenreader bieten zusätzliche Tastenkombinationen und Tastenbelegungen
+an, mit denen schnell zu Textabsätzen, Tabellen, Regionen der Seite oder
+Links navigiert werden kann. Diese sind jedoch je nach verwendetem
+Screenreader unterschiedlich.
+
+
+### Elemente tastaturbedienbar machen
+
+HTML-Elemente, die standardmäßig nicht tastaturbedienbar sind, können über das
+Attribut „tabindex“ tastaturbedienbar gemacht werden. Hier als Beispiel für ein
+LABEL-Element:
+
+ <label for="some-element" tabindex="0">
+ ....
+ </label>
+
+Das „tabindex“-Attribut mit dem Wert 0 fügt das LABEL-Element in die Liste mit
+fokussierbaren Elementen ein.
+
+Der Wert für das „tabindex“-Attribut soll auf 0 gesetzt sein, wenn ein
+standardmäßig nicht fokussierbares Element fokussierbar gemacht werden soll.
+Für den umgekerten Fall, bei dem ein fokussierbares Element nicht fokussierbar
+gemacht werden soll, beträgt der Wert des „tabindex“-Attributes -1.
+
+Andere Werte als 0 und -1 sollten für tabindex nicht verwenden werden, weil sie
+in die „natürliche“ Reihenfolge der fokussierbaren Elemente eingreifen und zu
+unerwarteten Sprüngen führen können, wenn zwei Elemente in unterschiedlichen
+Seitenbereichen eine feste Nummer im Tabindex bekommen haben. Beispiel:
+
+ <main>
+ <div tabindex="2">E1</div>
+ ...
+ <a href=".../e2.php" tabindex="4">E2</a>
+ </main>
+ <footer>
+ <a href=".../f1.php" tabindex="1">F1</a>
+ <button tabindex="3">F2</button>
+ </footer>
+
+Die Reihenfolge der Fokussierbaren Elemente wäre hier: F1, E1, F2, E2 statt
+der „natürlichen“ Reihenfolge E1, E2, F1, F2.
+
+
+### „click“-Event auch beim Drücken der Eingabetaste auslösen
+
+Seit der Behebung von [BIESt 106](https://gitlab.studip.de/studip/studip/-/issues/106)
+gibt es ein Stück generellen JavaScript-Code, der bei allen Elementen aktiv
+wird, die der Klasse „enter-accessible“ angehören. Wird solch ein Element
+fokussiert und die Eingabetaste gedrückt, wird das click-Event ausgelöst,
+das normalerweise nur bei Mausklicks ausgelöst wird.
+Das LABEL-Element aus dem Beispiel von oben müsste so erweitert werden:
+
+ <label for="some-element" class="enter-accessible" tabindex="0">
+ ....
+ </label>
+
+
+### Skiplinks
+
+Skiplinks erleichern die Bedienung einer Seite, da man dank ihnen direkt zum
+interessanten Bereich der Seite springen kann und nicht gezwungen ist, alle
+Elemente einer Seite in der üblichen Reihenfolge durchzugehen, bis man das
+richtige Element gefunden hat.
+
+In Stud.IP hilft die SkipLink-Klasse bei der Bereitstellung dieser
+Funktionalität. Es lassen sich damit bestimmte Seitenelemente anspringen und
+fokussieren. Wird ein Skiplink hinzugefügt, kann dessen Position ebenfalls
+festgelegt werden.
+
+#### Standardmäßig vorhandenen Skiplinks und deren Positionen
+
+Folgende Skiplinks sind standardmäßig im Stud.IP aktiviert:
+
+| Name | Element-ID | Position** |
+| ------------------------- | ------------------------ | ---------- |
+| Profilmenü | header_avatar_image_link | 1 |
+| Hauptnavigation | barTopMenu | 2 |
+| Zweite Navigationsebene* | tabs | 10 |
+| Dritte Navigationsebene** | nav_layer_3 | 20 |
+| Aktionen** | sidebar_actions | 21 |
+| Hauptinhalt | layout_content | 100 |
+| Fußzeile** | layout_footer | 900 |
+| Suche** | globalsearch-input | 910 |
+| Tipps & Hilfe** | helpbar_icon | 920 |
+
+*= bis Stud.IP 5.0: Erste Reiternavigation
+
+**=erst ab Stud.IP 5.1
+
+#### Skiplink hinzufügen
+
+Es können z.B. in Plugins zusätzliche Skiplinks hinzugefügt werden, wenn diese
+auf ein Element verweisen, das schnell erreichbar sein soll. Dazu wird die
+SkipLink-Klasse folgendermaßen aufgerufen:
+
+ //Füge den Skiplink mit der Beschriftung „Neuer Skiplink“ hinzu, der auf
+ //das Element mit der ID „id_zum_element“ verweist. Der Skiplink soll an
+ //die Position 200 gesetzt werden und nicht von anderen Codestellen
+ //überschreibbar sein (false):
+ SkipLinks::addIndex('Neuer Skiplink', 'id_zum_element', 200, false);
+
+Neue Skiplinks sollten nur sparsam hinzugefügt werden und nicht zwischen den
+Skiplinks des Kernsystems platziert werden, damit das „Muskelgedächtnis“
+für die Standard-Skiplinks bei der Bedienung mit der Tastatur funktionieren
+kann.
+
+
+## Verwendung von ARIA-Rollen und -Landmarks
+
+Nicht alle ARIA-Rollen können sinnvoll in Stud.IP verwendet werden.
+
+### „menu“ und „menuitem“ nicht verwenden
+
+Die Rollen „menu“ und „menuitem“ sollten in Stud.IP nicht verwendet werden.
+Hintergrund ist, dass „menu“ ein Menü beschreibt, das genauso bedienbar sein
+soll, wie das Menü einer Desktop-Anwendung: Pfeiltasten statt Tab oder
+Umschalt-Tab. Bei einem HTML-Element mit der Rolle „menu“ sagen Screenreader
+wie JAWS, das Element sei mit Pfeiltasten bedienbar, was im Stud.IP-Kontext bei
+keinem Menü der Fall ist.
+
+Mehr Informationen zur Problematik der „menu“ und „menuitem“-Rollen
+findet sich hier: https://adrianroselli.com/2017/10/dont-use-aria-menu-roles-for-site-nav.html
+
+
+## Testen mit Screenreadern
+
+Um zu testen, dass eine Entwicklung auch für Blinde oder Personen mit sehr
+eingeschränkter Sicht nutzbar ist, können Screenreader genutzt werden.
+Mit ihnen wird der Inhalt einer Seite vorgelesen. Über besondere
+Tastenkombinationen können bestimmte Elemente einer Seite direkt angesprungen
+werden, um eine langwierige Navigation über die Tabulator-Taste zu vermeiden.
+
+## Welche Screenreader gibt es?
+
+* Für Windows: JAWS oder NVDA
+* Für Mac OS X und iOS: VoiceOver
+* Für GNU/Linux: Orca
+* Für Android: TalkBack in Kombination mit eSpeak
+
+## Kombinationen von Screenreadern und Browsern
+
+Nicht alle Screenreader funktionieren mit allen Browsern gut. Folgende
+Kombinationen haben sich bei Tests als „harmonierend“ erwiesen:
+
+* JAWS mit Microsoft Edge
+* Orca mit Chromium
+
+
+## Tipps & Tricks
+
+### Checkboxen und Radio-Buttons verstecken
+
+Wenn man Checkboxen bzw. Radio-Buttons verstecken möchte, sollte man das nicht
+mit display:none oder visibility:hidden tun, denn dann sind die dazugehörigen
+Inhalte nicht mehr mit der Tastatur fokussierbar.
+
+Eine browserübergreifende Lösung ist Folgendes:
+
+ position: fixed;
+ opacity: 0;
+ pointer-events: none;
+
+Es ist auch nicht ratsam, anstelle der regulären Checkboxen eigene Grafiken
+zu verwenden.
+
+
+
+## Weiterführende Links
+
+### Farben und Kontraste
+
+- Kontrastrechner zur Berechnung und Überprüfung barrierefreier
+ Farbkombinationen: https://www.leserlich.info/werkzeuge/kontrastrechner/
+
+### ARIA-Rollen
+
+- https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques
diff --git a/docs/docs/assets/06e2031cec109992fa2df034e5c65642/CLIHelp.png b/docs/docs/assets/06e2031cec109992fa2df034e5c65642/CLIHelp.png
new file mode 100644
index 0000000..1a9c0dc
--- /dev/null
+++ b/docs/docs/assets/06e2031cec109992fa2df034e5c65642/CLIHelp.png
Binary files differ
diff --git a/docs/docs/assets/0ca19cdf7ae62c47c4a74b0110030059/dummy_plugin-v0.3.zip b/docs/docs/assets/0ca19cdf7ae62c47c4a74b0110030059/dummy_plugin-v0.3.zip
new file mode 100644
index 0000000..bb1138f
--- /dev/null
+++ b/docs/docs/assets/0ca19cdf7ae62c47c4a74b0110030059/dummy_plugin-v0.3.zip
Binary files differ
diff --git a/docs/docs/assets/13c6d38bf74b403424e42c16f8308bfe/Bildschirmfoto_2021-11-12_um_11.04.40.png b/docs/docs/assets/13c6d38bf74b403424e42c16f8308bfe/Bildschirmfoto_2021-11-12_um_11.04.40.png
new file mode 100644
index 0000000..99bc784
--- /dev/null
+++ b/docs/docs/assets/13c6d38bf74b403424e42c16f8308bfe/Bildschirmfoto_2021-11-12_um_11.04.40.png
Binary files differ
diff --git a/docs/docs/assets/16a10521ad60ae379906d2c9a8dab608/image.png b/docs/docs/assets/16a10521ad60ae379906d2c9a8dab608/image.png
new file mode 100644
index 0000000..d56ef6c
--- /dev/null
+++ b/docs/docs/assets/16a10521ad60ae379906d2c9a8dab608/image.png
Binary files differ
diff --git a/docs/docs/assets/1afde9c989ac66fc23192c4e7ca6aea8/image.png b/docs/docs/assets/1afde9c989ac66fc23192c4e7ca6aea8/image.png
new file mode 100644
index 0000000..8990a6f
--- /dev/null
+++ b/docs/docs/assets/1afde9c989ac66fc23192c4e7ca6aea8/image.png
Binary files differ
diff --git a/docs/docs/assets/1cde32e97e840ad35cd7840e4d61b016/image.png b/docs/docs/assets/1cde32e97e840ad35cd7840e4d61b016/image.png
new file mode 100644
index 0000000..1f015fc
--- /dev/null
+++ b/docs/docs/assets/1cde32e97e840ad35cd7840e4d61b016/image.png
Binary files differ
diff --git a/docs/docs/assets/1ce06d185917489aa6f3da79f9a491d6/image.png b/docs/docs/assets/1ce06d185917489aa6f3da79f9a491d6/image.png
new file mode 100644
index 0000000..2caa170
--- /dev/null
+++ b/docs/docs/assets/1ce06d185917489aa6f3da79f9a491d6/image.png
Binary files differ
diff --git a/docs/docs/assets/30d74c57a521f139c0050de6d55866a7/image.png b/docs/docs/assets/30d74c57a521f139c0050de6d55866a7/image.png
new file mode 100644
index 0000000..0298956
--- /dev/null
+++ b/docs/docs/assets/30d74c57a521f139c0050de6d55866a7/image.png
Binary files differ
diff --git a/docs/docs/assets/31dccb807d8186506f761674e5637696/Bildschirmfoto_2021-11-12_um_14.12.17.png b/docs/docs/assets/31dccb807d8186506f761674e5637696/Bildschirmfoto_2021-11-12_um_14.12.17.png
new file mode 100644
index 0000000..09df13f
--- /dev/null
+++ b/docs/docs/assets/31dccb807d8186506f761674e5637696/Bildschirmfoto_2021-11-12_um_14.12.17.png
Binary files differ
diff --git a/docs/docs/assets/3310a5850c6c4ed2d0b55a1884e5a39b/config_local.inc.php b/docs/docs/assets/3310a5850c6c4ed2d0b55a1884e5a39b/config_local.inc.php
new file mode 100644
index 0000000..f018604
--- /dev/null
+++ b/docs/docs/assets/3310a5850c6c4ed2d0b55a1884e5a39b/config_local.inc.php
@@ -0,0 +1,38 @@
+<?php
+/*basic settings for Stud.IP
+----------------------------------------------------------------
+you find here the basic system settings. You shouldn't have to touch much of them...
+please note the CONFIG.INC.PHP for the indivual settings of your installation!*/
+
+namespace Studip {
+ const ENV = 'development';
+}
+
+namespace {
+ /*settings for database access
+ ----------------------------------------------------------------
+ please fill in your database connection settings.
+ */
+
+ // default Stud.IP database (DB_Seminar)
+ $DB_STUDIP_HOST = 'localhost';
+ $DB_STUDIP_USER = 'root';
+ $DB_STUDIP_PASSWORD = '';
+ $DB_STUDIP_DATABASE = 'studip';
+
+ $TMP_PATH = $STUDIP_BASE_PATH . "/tmp";
+ $CACHING_ENABLE = false;
+ $MAIL_TRANSPORT = 'debug';
+ define("LC_MESSAGES", 5);
+ $CONTENT_LANGUAGES['en_GB'] = array('picture' => 'lang_en.gif', 'name' => 'English');
+
+ /*URL
+ ----------------------------------------------------------------
+ customize if automatic detection fails, e.g. when installation is hidden
+ behind a proxy
+ */
+ //$CANONICAL_RELATIVE_PATH_STUDIP = '/';
+ //$ABSOLUTE_URI_STUDIP = 'http://localhost/test/public/';
+ //$ASSETS_URL = 'https://www.studip.de/assets/';
+
+}
diff --git a/docs/docs/assets/3a528f8f2de835a0ba5c4e342929179a/image.png b/docs/docs/assets/3a528f8f2de835a0ba5c4e342929179a/image.png
new file mode 100644
index 0000000..729387f
--- /dev/null
+++ b/docs/docs/assets/3a528f8f2de835a0ba5c4e342929179a/image.png
Binary files differ
diff --git a/docs/docs/assets/3dd1c5b5758d79e52a77165d721e1665/image.png b/docs/docs/assets/3dd1c5b5758d79e52a77165d721e1665/image.png
new file mode 100644
index 0000000..c122c17
--- /dev/null
+++ b/docs/docs/assets/3dd1c5b5758d79e52a77165d721e1665/image.png
Binary files differ
diff --git a/docs/docs/assets/3ef926a748f8a39a8e44423cbd561786/image.png b/docs/docs/assets/3ef926a748f8a39a8e44423cbd561786/image.png
new file mode 100644
index 0000000..fc94a03
--- /dev/null
+++ b/docs/docs/assets/3ef926a748f8a39a8e44423cbd561786/image.png
Binary files differ
diff --git a/docs/docs/assets/45ecdce4e78d28ce8b9d1f4e42566069/image.png b/docs/docs/assets/45ecdce4e78d28ce8b9d1f4e42566069/image.png
new file mode 100644
index 0000000..7b65001
--- /dev/null
+++ b/docs/docs/assets/45ecdce4e78d28ce8b9d1f4e42566069/image.png
Binary files differ
diff --git a/docs/docs/assets/476d123391bbc2e4a825a1ea146a8465/image.png b/docs/docs/assets/476d123391bbc2e4a825a1ea146a8465/image.png
new file mode 100644
index 0000000..877e08e
--- /dev/null
+++ b/docs/docs/assets/476d123391bbc2e4a825a1ea146a8465/image.png
Binary files differ
diff --git a/docs/docs/assets/4bc68dbe8b01745976ff8b18aa025de6/image.png b/docs/docs/assets/4bc68dbe8b01745976ff8b18aa025de6/image.png
new file mode 100644
index 0000000..197aefe
--- /dev/null
+++ b/docs/docs/assets/4bc68dbe8b01745976ff8b18aa025de6/image.png
Binary files differ
diff --git a/docs/docs/assets/4c211f7ead854530fec8791e1f58589e/image.png b/docs/docs/assets/4c211f7ead854530fec8791e1f58589e/image.png
new file mode 100644
index 0000000..d216eb6
--- /dev/null
+++ b/docs/docs/assets/4c211f7ead854530fec8791e1f58589e/image.png
Binary files differ
diff --git a/docs/docs/assets/57d2b4e9802cbb4c83816956d7d53a75/image.png b/docs/docs/assets/57d2b4e9802cbb4c83816956d7d53a75/image.png
new file mode 100644
index 0000000..c03c9bd
--- /dev/null
+++ b/docs/docs/assets/57d2b4e9802cbb4c83816956d7d53a75/image.png
Binary files differ
diff --git a/docs/docs/assets/69756a9f57c09cc3f3450519af957b23/dateiliste.png b/docs/docs/assets/69756a9f57c09cc3f3450519af957b23/dateiliste.png
new file mode 100644
index 0000000..9dfb927
--- /dev/null
+++ b/docs/docs/assets/69756a9f57c09cc3f3450519af957b23/dateiliste.png
Binary files differ
diff --git a/docs/docs/assets/6a365d838099cd2de468d98e0844f780/image.png b/docs/docs/assets/6a365d838099cd2de468d98e0844f780/image.png
new file mode 100644
index 0000000..662752c
--- /dev/null
+++ b/docs/docs/assets/6a365d838099cd2de468d98e0844f780/image.png
Binary files differ
diff --git a/docs/docs/assets/6d14189aa9093eb042bfa56eae8c7dc2/170804_Studip-Farbset.pdf b/docs/docs/assets/6d14189aa9093eb042bfa56eae8c7dc2/170804_Studip-Farbset.pdf
new file mode 100644
index 0000000..a8bdab0
--- /dev/null
+++ b/docs/docs/assets/6d14189aa9093eb042bfa56eae8c7dc2/170804_Studip-Farbset.pdf
Binary files differ
diff --git a/docs/docs/assets/6feeded7490d702b510a71a924934269/footer.png b/docs/docs/assets/6feeded7490d702b510a71a924934269/footer.png
new file mode 100644
index 0000000..3829448
--- /dev/null
+++ b/docs/docs/assets/6feeded7490d702b510a71a924934269/footer.png
Binary files differ
diff --git a/docs/docs/assets/734bff664ced438cfa362a79142ecddb/image.png b/docs/docs/assets/734bff664ced438cfa362a79142ecddb/image.png
new file mode 100644
index 0000000..ffae6d3
--- /dev/null
+++ b/docs/docs/assets/734bff664ced438cfa362a79142ecddb/image.png
Binary files differ
diff --git a/docs/docs/assets/829b531d3dbd3e2bde37443f1394f821/image.png b/docs/docs/assets/829b531d3dbd3e2bde37443f1394f821/image.png
new file mode 100644
index 0000000..043bc47
--- /dev/null
+++ b/docs/docs/assets/829b531d3dbd3e2bde37443f1394f821/image.png
Binary files differ
diff --git a/docs/docs/assets/8fcb015158becfc136f6d2db84ef27bf/image.png b/docs/docs/assets/8fcb015158becfc136f6d2db84ef27bf/image.png
new file mode 100644
index 0000000..ebd04a4
--- /dev/null
+++ b/docs/docs/assets/8fcb015158becfc136f6d2db84ef27bf/image.png
Binary files differ
diff --git a/docs/docs/assets/a0377c72d0325b7d57151aca9ad52143/dialog.png b/docs/docs/assets/a0377c72d0325b7d57151aca9ad52143/dialog.png
new file mode 100644
index 0000000..d5c0b45
--- /dev/null
+++ b/docs/docs/assets/a0377c72d0325b7d57151aca9ad52143/dialog.png
Binary files differ
diff --git a/docs/docs/assets/bbe72ca0392b9f0f40989e5fabba6e14/konfiguration.png b/docs/docs/assets/bbe72ca0392b9f0f40989e5fabba6e14/konfiguration.png
new file mode 100644
index 0000000..65ff5ae
--- /dev/null
+++ b/docs/docs/assets/bbe72ca0392b9f0f40989e5fabba6e14/konfiguration.png
Binary files differ
diff --git a/docs/docs/assets/c061aa06f3acdf6003dc54bd1e677ebe/image.png b/docs/docs/assets/c061aa06f3acdf6003dc54bd1e677ebe/image.png
new file mode 100644
index 0000000..9b81dae
--- /dev/null
+++ b/docs/docs/assets/c061aa06f3acdf6003dc54bd1e677ebe/image.png
Binary files differ
diff --git a/docs/docs/assets/c36105bd58464c5eb00e596123af30c8/CLIHelpOnCommands.png b/docs/docs/assets/c36105bd58464c5eb00e596123af30c8/CLIHelpOnCommands.png
new file mode 100644
index 0000000..89548f9
--- /dev/null
+++ b/docs/docs/assets/c36105bd58464c5eb00e596123af30c8/CLIHelpOnCommands.png
Binary files differ
diff --git a/docs/docs/assets/c37f69398215d78b12784d3428c89a9c/Bildschirmfoto_2021-11-15_um_15.35.11.png b/docs/docs/assets/c37f69398215d78b12784d3428c89a9c/Bildschirmfoto_2021-11-15_um_15.35.11.png
new file mode 100644
index 0000000..55bd4b4
--- /dev/null
+++ b/docs/docs/assets/c37f69398215d78b12784d3428c89a9c/Bildschirmfoto_2021-11-15_um_15.35.11.png
Binary files differ
diff --git a/docs/docs/assets/ce19cb16e52e35fa80ad2fd66ee7fbac/image.png b/docs/docs/assets/ce19cb16e52e35fa80ad2fd66ee7fbac/image.png
new file mode 100644
index 0000000..392a7be
--- /dev/null
+++ b/docs/docs/assets/ce19cb16e52e35fa80ad2fd66ee7fbac/image.png
Binary files differ
diff --git a/docs/docs/assets/d853edb1d90bc82e3dc0d69fcb2927b4/image.png b/docs/docs/assets/d853edb1d90bc82e3dc0d69fcb2927b4/image.png
new file mode 100644
index 0000000..4bda1c7
--- /dev/null
+++ b/docs/docs/assets/d853edb1d90bc82e3dc0d69fcb2927b4/image.png
Binary files differ
diff --git a/docs/docs/assets/dc7253456d69ace6eb97258059ce0a87/image.png b/docs/docs/assets/dc7253456d69ace6eb97258059ce0a87/image.png
new file mode 100644
index 0000000..7c1d2c9
--- /dev/null
+++ b/docs/docs/assets/dc7253456d69ace6eb97258059ce0a87/image.png
Binary files differ
diff --git a/docs/docs/assets/fbce782c9fa1a8778926c5f6ade1d5d4/image.png b/docs/docs/assets/fbce782c9fa1a8778926c5f6ade1d5d4/image.png
new file mode 100644
index 0000000..ec52325
--- /dev/null
+++ b/docs/docs/assets/fbce782c9fa1a8778926c5f6ade1d5d4/image.png
Binary files differ
diff --git a/docs/docs/assets/fe20666fc8035cf25e6e65e3a36bd83b/image.png b/docs/docs/assets/fe20666fc8035cf25e6e65e3a36bd83b/image.png
new file mode 100644
index 0000000..89436c1
--- /dev/null
+++ b/docs/docs/assets/fe20666fc8035cf25e6e65e3a36bd83b/image.png
Binary files differ
diff --git a/docs/docs/assets/hello_axel.png b/docs/docs/assets/hello_axel.png
new file mode 100644
index 0000000..d2d6fdf
--- /dev/null
+++ b/docs/docs/assets/hello_axel.png
Binary files differ
diff --git a/docs/docs/assets/layout.png b/docs/docs/assets/layout.png
new file mode 100644
index 0000000..c63bcee
--- /dev/null
+++ b/docs/docs/assets/layout.png
Binary files differ
diff --git a/docs/docs/assets/quotes.png b/docs/docs/assets/quotes.png
new file mode 100644
index 0000000..47b8a76
--- /dev/null
+++ b/docs/docs/assets/quotes.png
Binary files differ
diff --git a/docs/docs/coding-style.md b/docs/docs/coding-style.md
new file mode 100644
index 0000000..b4bb01a
--- /dev/null
+++ b/docs/docs/coding-style.md
@@ -0,0 +1,527 @@
+---
+title: Coding Style
+---
+
+### Geltungsbereich
+
+Dieses Dokument bietet Richtlinien für die Formatierung von Code und Dokumentation für Entwickler, die an Stud.IP mitarbeiten. Die folgenden Bereiche werden vom Stud.IP Coding Standard abgedeckt:
+
+
+## PHP Dateiformatierung
+
+Für Dateien, die nur PHP Code beinhalten ist der schliessende Tag ("?>") nicht zugelassen. Er wird von PHP nicht benötigt, und das weglassen verhindert, dass versehentlich Leerzeilen in die Antwort eingefügt werden.
+
+**WICHTIG:** Einbeziehen von beliebigen binären Daten durch __HALT_COMPILER() ist in den PHP Dateien verboten. Das Benutzen ist nur für einige Installationsskripte erlaubt.
+
+## Einrücken
+
+Ein Einzug sollte aus 4 Leerzeichen bestehen. Tabulatoren sind nicht erlaubt.
+
+## Maximale Zeilenlänge
+
+Die Zielzeilenlänge ist 80 Zeichen. Entwickler sollten jede Zeile Ihres Codes unter 80 Zeichen halten, soweit dies möglich und praktikabel ist. Trotzdem sind längere Zeilen in einigen Fällen erlaubt. Die maximale Länge einer Zeile beträgt 120 Zeichen.
+
+## Zeilenbegrenzung
+
+Die Zeilenbegrenzung folgt der Unix-Textdateikonvention. Zeilen müssen mit einem einzelnen Zeilenvorschubzeichen (LF) enden. Zeilenvorschubzeichen werden duch eine 10 (dezimal) bzw. durch 0x0A (hexadezimal) dargestellt.
+
+Beachte: Benutzen Sie nicht den Wagenrücklauf (CR &#8594; 0x0D) oder die Kombination aus Wagenrücklauf und Zeilenvorschub (CRLF &#8594; 0x0D 0x0A).
+
+
+## Namenskonventionen
+
+### Klassen
+
+Klassennamen dürfen nur alphanumerische Zeichen enthalten. Nummern sind in Klassennamen gestattet, es wird aber in den meisten Fällen davon abgeraten.
+
+Wenn ein Klassenname aus mehr als einem Wort besteht, muß der erste Buchstabe von jedem neuen Wort großgeschrieben werden.
+
+Um Pseudo-Namensräume zu definieren, dürfen in Klassennamen einzelne Unterstriche verwendet werden.
+
+Beispiel: `class Trails_Controller`
+
+Sobald echte Namensräume verfügbar sind, müssen diese Pseudo-Namensräume entsprechend ersetzt werden.
+
+### Dateinamen
+
+In Dateinamen sind nur alphanumerische Zeichen ("a-zA-Z0-9"), Unterstriche ("_"), Bindestriche ("-") und Punkte (".") gestattet. Leerzeichen sind völlig verboten.
+
+Jede Datei die PHP-Code enthält, sollte mit der Endung ".php" enden.
+
+Dateinamen müssen den Klassennamen wie oben beschrieben entsprechen.
+
+### Funktionen und Methoden
+
+Methodennamen dürfen nur alphanumerische Zeichen enthalten. Unterstriche sind nicht gestattet. Ziffern sind in Funktionsnamen gestattet, aber in den meisten Fällen nicht empfohlen.
+
+Funktions- und Methodennamen müssen immer mit einem Kleinbuchstaben anfangen. Wenn ein Methodenname aus mehr als einem Wort bestehen, muß der erste Buchstabe eines jeden Wortes großgeschrieben werden. Das wird üblicherweise "camelCase"-Formatierung genannt.
+
+Wortreichtum wird generell befürwortet. Funktionsnamen sollten so wortreich wie möglich sein, um deren Zweck und Verhalten zu erklären.
+
+Beispiele für Methodenamen:
+
+```php
+filterInput()
+
+getElementById()
+
+widgetFactory()
+```
+
+Für objekt-orientiertes Programmieren sollten Zugriffsmethoden für Instanz- oder Klassenvariablen immer mit `get` oder `set` beginnen. Wenn Design-Pattern implementiert werden sollte, sollte der Name der Methode den Konventionen des Patterns entsprechen, um das Verhalten besser zu beschreiben.
+
+Globale Funktionen sind gestattet, aber es wird von ihnen in den meisten Fällen abgeraten. Diese Funktionen sollten in einer statischen Klasse gekapselt werden.
+
+### Variablen
+
+Variablennamen dürfen nur alphanumerische Zeichen und den Unterstrich enthalten. Ziffern sind in Variablen gestattet, in den meisten Fällen aber nicht empfohlen.
+
+Wie bei Funktionsnamen (siehe oben) müssen Variablennamen immer mit einem Kleinbuchstaben anfangen.
+
+Sprechende Bezeichner werden generell befürwortet. Variablen sollen immer so wortreich wie möglich sein, um die Daten zu beschreiben, die der Entwickler in ihnen zu speichern gedenkt. Von sehr kurzen Variablennamen wie `$i` und `$n` wird abgesehen von der Verwendung in kleinen Schleifen abgeraten. Wenn eine Schleife mehr als 20 Codezeilen enthält, sollten die Index-Variablen einen ausführlicheren Namen haben.
+
+### Konstanten
+
+Konstantenbezeichner können alphanumerische Zeichen und Unterstriche enthalten.
+
+Alle Buchstaben, die in Konstantenname verwendet werden, müssen großgeschrieben werden. Wörter in einem Konstantennamen müssen durch Unterstriche getrennt werden.
+
+Beispiel: `EMBED_SUPPRESS_EMBED_EXCEPTION` ist gestattet,`EMBED_SUPPRESSEMBEDEXCEPTION` jedoch nicht.
+
+Konstanten müssen als Klassenkonstanten (Schlüsselwort "const") definiert werden. Die Definition von Konstanten mit der `define` Funktion im globalen Bereich ist gestattet, jedoch wird davon stark abgeraten.
+
+
+## PHP Code-Abgrenzung
+
+PHP Code muß immer mit der kompletten Form des Standard-PHP Tags abgegrenzt sein:
+
+```php
+<?php
+
+?>
+```
+
+Kurze Tags sind nur in Templates erlaubt. Für Dateien die nur PHP Code enthalten, darf das schließende Tag nie angegeben werden.
+
+## Strings
+### String-Literale
+
+Bei String-Literalen, wenn ein String also keine Variablen enthält, sollte immer das Apostroph "'" (single quote) verwendet werden um den String abzugrenzen:
+
+```php
+$aString = 'Example String';
+```
+
+
+## String-Literale mit Apostrophen
+
+Wenn ein String-Literal selbst Apostrophe enthält, ist es gestattet den String mit Anführungszeichen (double quotes) abzugrenzen. Das ist speziell für SQL-Anweisungen nützlich:
+
+```php
+$sql = "SELECT `id`, `name` from `people` "
+ . "WHERE `name`='Fred' OR `name`='Susan'";
+```
+
+Diese Syntax ist gegenüber dem Schützen des Apostrophs durch "\'" aus Gründen der besseren Lesbarkeit zu bevorzugen.
+
+### Variable Substitution
+
+Variable substitution is permitted using either of these forms:
+
+```php
+$greeting = "Hello $name, welcome back!";
+
+$greeting = "Hello {$name}, welcome back!";
+```
+
+
+
+For consistency, this form is not permitted:
+
+```php
+$greeting = "Hello ${name}, welcome back!";
+```
+
+
+## String-Konkatenation
+
+Strings müssen mit dem "."-Operator konkateniert werden. Ein Leerzeichen muß immer vor und nach dem "." Operator eingefügt werden, um die Lesbarkeit zu erhöhen:
+
+```php
+$company = 'Zend' . ' ' . 'Technologies';
+```
+
+Werden Strings mit dem "." Operator konkateniert, sollte die Anweisung in mehrere Zeilen umgebrochen werden, um die Lesbarkeit zu erhöhen. In diesen Fällen sollte jede folgende Zeile mit Leerraum aufgefüllt werden so das der "." Operator genau unterhalb des "=" Operators steht:
+
+```php
+$sql = "SELECT `id`, `name` FROM `people` "
+ . "WHERE `name` = 'Susan' "
+ . "ORDER BY `name` ASC ";
+```
+
+
+## Arrays
+### Numerisch indizierte Arrays
+
+Negative Indizes sind nicht gestattet. Ein solches Array darf mit einer nicht-negativen Zahl beginnen, es wird jedoch davon abgeraten.
+
+Werden indizierte Arrays mehrzeilig mit Hilfe der "array"-Funktion definiert, muß ein Leerzeichen nach jeder Kommabegrenzung folgen, um die Lesbarkeit zu erhöhen:
+
+```php
+$sampleArray = array(1, 2, 3, 'Zend', 'Studio');
+```
+
+Es ist gestattet, mehrzeilige indizierte Arrays mit der "array"-Funktion zu definieren. In diesem Fall, muß jede folgende Zeile mit Leerzeichen aufgefüllt werden so das der Beginn jeder Zeile ausgerichtet ist:
+
+```php
+$sampleArray = array(1, 2, 3, 'Zend', 'Studio',
+ $a, $b, $c,
+ 56.44, $d, 500);
+```
+
+
+### Assoziative Arrays
+
+Wenn assoziative Arrays mit der "array"-Funktion deklariert werden, ist das Umbrechen der Anweisung in mehrere Zeilen gestattet. In diesem Fall muß jede folgende Linie mit Leerraum aufgefüllt werden so das beide, der Schlüssel und der Wert, untereinander stehen:
+
+```php
+$sampleArray = array(
+ 'firstKey' => 'firstValue',
+ 'secondKey' => 'secondValue'
+);
+```
+
+
+## Klassen
+### Klassendeklaration
+
+Klassen müssen den Namenskonventionen entsprechend benannt werden.
+
+Die Klammer sollte immer in der Zeile unter dem Klassennamen geschrieben werden.
+
+Jede Klasse muß einen Dokumentationsblock haben der dem PHPDocumentor Standard entspricht.
+
+Jeder Code in der Klasse muß mit vier Leerzeichen eingerückt sein.
+
+Nur eine Klasse ist in jeder PHP Datei gestattet.
+
+Das Platzieren von zusätzlichem Code in Klassendateien ist gestattet, aber es wird davon abgeraten.
+
+Das folgende ist ein Beispiel einer gültige Klassendeklaration:
+
+```php
+/**
+ * Documentation Block Here
+ */
+class SampleClass
+{
+ // all contents of class
+ // must be indented four spaces
+}
+```
+
+
+### Klassenvariablen
+
+Klassenvariablen müssen entsprechend den Variablennamenskonventionen benannt werden.
+
+Jede Variable die in der Klasse deklariert wird muß am Beginn der Klasse aufgelistet werden, vor der Deklaration von allen Methoden.
+
+Das "var"-Schlüsselwort ist nicht gestattet. Klassenvariablen definieren ihre Sichtbarkeit durch die Verwendung der private, protected, oder public Modifikatoren. Öffentliche Klassenvariable (Sichtbarkeit "public") sind erlaubt, es wird aber zu Gunsten von Zugriffsmethoden (getter/setter) davon abgeraten.
+
+## Funktionen und Methoden
+### Deklaration von Funktionen und Methoden
+
+Funktionen müssen nach der Funktionsnamenskonvention benannt werden.
+
+Methoden innerhalb von Klassen müssen immer ihre Sichtbarkeit durch Verwendung eines der private, protected, oder public Modifikatoren definieren.
+
+Wie bei Klassen sollte die Klammer immer in der Zeile unterhalb des Funktionsnamens geschrieben werden. Leerzeichen zwischen dem Funktionsnamen und der öffnenden Klammer für die Argumente sind nicht erlaubt.
+
+Von globalen Funktionen wird abgeraten.
+
+Das folgende ist ein Beispiel einer gültigen Funktionsdeklaration in einer Klasse:
+
+```php
+/**
+ * Documentation Block Here
+ */
+class Foo
+{
+ /**
+ * Documentation Block Here
+ */
+ public function bar()
+ {
+ // all contents of function
+ // must be indented four spaces
+ }
+}
+```
+
+NOTE: Pass-by-reference is the only {+explicit+} parameter passing mechanism permitted in a method declaration.
+
+```php
+/**
+ * Documentation Block Here
+ */
+class Foo
+{
+ /**
+ * Documentation Block Here
+ */
+ public function bar(&$baz)
+ {}
+}
+```
+
+Call-time pass-by-reference ist strikt verboten.
+
+Der Rückgabewert darf nicht in Klammern stehen. Das kann die Lesbarkeit behindern und zusätzlich zu Fehlern führen, wenn eine Methode später auf Rückgabe durch Referenz geändert wird.
+
+```php
+/**
+ * Documentation Block Here
+ */
+class Foo
+{
+ /**
+ * WRONG
+ */
+ public function bar()
+ {
+ return($this->bar);
+ }
+
+ /**
+ * RIGHT
+ */
+ public function bar()
+ {
+ return $this->bar;
+ }
+}
+```
+
+
+### Aufruf von Funktionen and Methoden
+
+Wie bei Funktionsdeklaration darf zwischen Funktionsnamen und der öffnenden Klammer für die Argumente beim Funktionsaufruf kein Leerzeichen stehen.
+
+Funktionsargumente sollten durch ein einzelnes trennendes Leerzeichen nach dem Komma getrennt werden. Das folgende ist ein Beispiel für einen gültigen Aufruf einer Funktion die drei Argumente benötigt:
+
+```php
+threeArguments(1, 2, 3);
+```
+
+Call-time pass-by-reference is strictly prohibited.
+
+In passing arrays as arguments to a function, the function call may include the "array" hint and may be split into multiple lines to improve readability. In such cases, the normal guidelines for writing arrays still apply:
+
+```php
+threeArguments(array(1, 2, 3), 2, 3);
+
+threeArguments(array(1, 2, 3, 'Zend', 'Studio',
+ $a, $b, $c,
+ 56.44, $d, 500), 2, 3);
+```
+
+
+## Kontrollstrukturen
+### if/else/elseif
+
+Control statements based on the if and elseif constructs must have a single space before the opening parenthesis of the conditional and a single space after the closing parenthesis.
+
+Within the conditional statements between the parentheses, operators must be separated by spaces for readability. Inner parentheses are encouraged to improve logical grouping for larger conditional expressions.
+
+The opening brace is written on the same line as the conditional statement. The closing brace is always written on its own line. Any content within the braces must be indented using four spaces.
+
+```php
+if ($a != 2) {
+ $a = 2;
+}
+```
+
+
+
+For "if" statements that include "elseif" or "else", the formatting conventions are similar to the "if" construct. The following examples demonstrate proper formatting for "if" statements with "else" and/or "elseif" constructs:
+
+```php
+if ($a != 2) {
+ $a = 2;
+} else {
+ $a = 7;
+}
+
+if ($a != 2) {
+ $a = 2;
+} elseif ($a == 3) {
+ $a = 4;
+} else {
+ $a = 7;
+}
+```
+
+
+PHP allows statements to be written without braces in some circumstances. This coding standard makes no differentiation - all "if", "elseif" or "else" statements must use braces.
+
+Use of the "elseif" construct is permitted but strongly discouraged in favor of the "else if" combination.
+
+### Switch
+
+Control statements written with the "switch" statement must have a single space before the opening parenthesis of the conditional statement and after the closing parenthesis.
+
+All content within the "switch" statement must be indented using four spaces. Content under each "case" statement must be indented using an additional four spaces.
+
+```php
+switch ($numPeople) {
+ case 1:
+ break;
+
+ case 2:
+ break;
+
+ default:
+ break;
+}
+```
+
+
+The construct default should never be omitted from a switch statement.
+
+NOTE: It is sometimes useful to write a case statement which falls through to the next case by not including a break or return within that case. To distinguish these cases from bugs, any case statement where break or return are omitted should contain a comment indicating that the break was intentionally omitted.
+
+## Inline Documentation
+### Documentation Format
+
+All documentation blocks ("docblocks") must be compatible with the phpDocumentor format. Describing the phpDocumentor format is beyond the scope of this document. For more information, visit: http://phpdoc.org/
+
+All class files must contain a "file-level" docblock at the top of each file and a "class-level" docblock immediately above each class. Examples of such docblocks can be found below.
+
+### Files
+
+Every file that contains PHP code must have a docblock at the top of the file that contains these phpDocumentor tags at a minimum:
+
+```php
+/**
+ * Short description for file
+ *
+ * Long description for file (if any)...
+ *
+ * LICENSE: Some license information
+ *
+ * @author Vorname Nachname <email>
+ * @copyright 2008 Zend Technologies
+ * @license http://framework.zend.com/license BSD License
+ * @category Stud.IP
+*/
+```
+
+Optional tags:
+```php
+/**
+ * @package calendar
+ * @link http://framework.zend.com/package/PackageName
+ * @since File available since Release 1.5.0
+*/
+```
+
+### Classes
+
+Every class must have a docblock that contains {-these phpDocumentor tags-} at a minimum:
+
+```php
+/**
+ * Short description for class
+ *
+ * Long description for class (if any)...
+ *
+ */
+```
+
+Optional tags:
+```php
+/**
+ * @link http://framework.zend.com/package/PackageName
+ * @since Class available since Release 1.5.0
+ * @deprecated Class deprecated in Release 2.0.0
+ */
+```
+
+### Functions
+
+Every function, including object methods, must have a docblock that contains at a minimum:
+
+A description of the function
+
+All of the arguments
+
+All of the possible return values
+
+
+It is not necessary to use the "@access" tag because the access level is already known from the "public", "private", or "protected" modifier used to declare the function.
+
+If a function/method may throw an exception, use @throws for all known exception classes:
+
+```php
+@throws exceptionclass [description]
+```
+
+
+## Templates
+
+Für Templates gelten alle obigen Aussagen. Zusätzlich gelten aber folgende Regeln:
+
+Bei Short-Tag-Zuweisungen muss nach dem eröffnenden und vor dem schließenden Tag genau ein Leerzeichen eingefügt werden:
+
+```php
+<div class="<?= $css_class ?>"></div>
+```
+
+Semikola werden nicht verwendet.
+
+Zur Steigerung der Lesbarkeit können die alternativen Kontrollstrukturen verwendet werden:
+
+```php
+<? if (true) : ?>
+...
+<? else : ?>
+...
+<? endif ?>
+
+<? foreach ($array() as $key => $value) : ?>
+...
+<? endforeach ?>
+
+usw.
+```
+
+Dabei ist zu beachten, dass die Doppelpunkte mit je einem Leerzeichen umschlossen werden. Die abschließenden `endif`, `endforeach` usw. dürfen (genau wie bei den sonst üblichen {}) nicht mit einem Semikolon beendet werden.
+
+
+
+### Ziele
+
+Coding Standards sind in jedem Softwareprojekt wichtig, insbesondere wenn viele Entwickler daran arbeiten. Coding Standards helfen sicherzustellen, dass der Code von hoher Qualität ist, weniger Fehler hat und einfach zu warten ist.
+
+
+### Pagelevel-Doc-Block für copy&paste
+
+Dieser Absatz ist nicht-normativ.
+
+```php
+/**
+ * filename - Short description for file
+ *
+ * Long description for file (if any)...
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author name <email>
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ */
+```
diff --git a/docs/docs/functions/actionmenu.md b/docs/docs/functions/actionmenu.md
new file mode 100644
index 0000000..84be174
--- /dev/null
+++ b/docs/docs/functions/actionmenu.md
@@ -0,0 +1,90 @@
+---
+title: Aktionsmenü
+sidebar_label: Aktionsmenüs
+---
+# ActionMenu
+
+Am `ActionMenu` können Aktionsicons in ein ausklappbares Menü zusammengefasst werden.
+
+## PHP
+
+Die Klasse kann direkt innerhalb einer View verwendet werden, weil sie den nötigen HTML-Code generiert und ausgibt.
+
+Über `ActionMenu::get()` erhält man eine Instanz der Klasse, die mit Inhalten befüllt werden kann.
+
+Über die Methode `addLink` werden neue Aktionslinks hinzugefügt, die wie üblich mit den Parametern URL, Label, Icon und weiteren Optionen spezifiziert werden.
+
+Weiter gibt es die Methode `addMultiPersonSearch`, die als Parameter ein `MultiPersonSearch-Objekt` erhält und diese Suche als Aktion hinzufügt.
+
+Ein minimaler Aufruf mit einer einzigen Aktion könnte also so aussehen:
+
+```php
+<?php
+$menu = ActionMenu::get();
+$menu->addLink(
+ $controller->url_for('controller/action'),
+ _('Hinzufügen'),
+ Icon::create('add'),
+ ['data-dialog' => 'size=auto']
+);
+$menu->addLink(
+ $controller->url_for('controller/second_action'),
+ _('Bearbeiten'),
+ Icon::create('edit'),
+ ['data-dialog' => 'size=auto']
+);
+$menu->addButton(
+ 'delete',
+ ('Löschen'),
+ Icon::create('trash'),
+ [
+ 'data-confirm' => _('Wollen Sie wirklich löschen?'),
+ 'formaction' => $controller->url_for('controller/delete')
+ ]
+);
+$menu->addMultiPersonSearch(
+ MultiPersonSearch::get('add_users')
+ ->setTitle(_('Personen hinzufügen'))
+ ->setLinkText(_('Personen hinzufügen'))
+ ->setSearchObject($array)
+ ->setDefaultSelectedUser($array_selected_user)
+ ->setDataDialogStatus(Request::isXhr())
+ ->setJSFunctionOnSubmit(Request::isXhr() ? 'STUDIP.Dialog.close();' : false)
+ ->setExecuteURL($controller->url_for('controller/add_member/'))
+ ->addQuickfilter(_('Titel'), $array)
+);
+echo $menu->render();
+?>
+```
+
+## Vue
+
+Die Vue-Komponente wird über das Tag `StudipActionMenu` eingebunden und hat die folgenden Properties:
+
+```json
+{
+ "collapseAt": "Schwellwert, ab dem das Menü als tatsächliches Menü angezeigt wird [Number] (optional)",
+ "context": "Optional Kontext, der über den Einträgen angezeigt wird [String]",
+ "items": [],
+ "title": "Titel des Aktionsmenüs [String] (optional, Default: 'Aktionsmenü')"
+}
+```
+
+Der Wert für `collapseAt` ist optional und wenn er nicht angegeben wird, wird auf den Stud.IP-Default zurückgegriffen.
+
+Das `items`-Array besteht dabei aus Einträgen des folgendes Formats:
+
+```json
+{
+ "label": "Text des Eintrags [String]",
+ "url": "URL, die aufgerufen werden soll, wenn der Eintrag ausgewählt wird [String] (optional, default: '#')",
+ "emit": "Event, der gefeuert werden soll, wenn der Eintrag ausgewählt wird [String] (optional)",
+ "emitArgument": "Argumente, die dem Event mitgegeben werden sollen, wenn dieser gefeuert wird [Array] (optional)",
+ "icon": "Icon, das für den Eintrag angezeigt werden soll [Objekt: {shape: String}] oder false, wenn kein Icon angegeben werden soll (optional, default: false)",
+ "type": "Möglicher Typ des Eintrags: 'link', 'button', 'separator' [String] (optional, default: 'link')",
+ "name": "Name des Buttons; Eintrag wird hierdurch automatisch zu einem Button, wenn keine 'url' gesetzt ist [String] (optional)",
+ "classes": "CSS-Klassen, die bei dem Eintrag gesetzt sein sollen [String] (optional)",
+ "attributes": "Weitere HTML-Attribute, die bei dem Eintrag gesetzt sein sollen [Objekt] (optional)",
+ "disabled": "Gibt an, ob der Eintrag deaktiviert sein soll [Boolean] (optional)"
+}
+```
diff --git a/docs/docs/functions/activity-api.md b/docs/docs/functions/activity-api.md
new file mode 100644
index 0000000..a5accbe
--- /dev/null
+++ b/docs/docs/functions/activity-api.md
@@ -0,0 +1,193 @@
+---
+id: activity-api
+title: Activity API
+sidebar_label: Activity API
+---
+
+# Activity API
+
+Mit Stud.IP Version 3.5 wurde eine neue API zum Erzeugen, Darstellen und Filtern von kontextrelevanten Aktivitäten eingeführt. Diese API kann u.a. dafür genutzt werden um Nutzern einen schnellen Überblick über die für ihn relevanten Information/Aktivitäten zu geben.
+
+## Activity-Streams
+
+Der Activity-Stream befindet sich auf oberster Ebene der API. Möchte man einen Activity-Stream haben:
+
+```php
+$stream = new \Studip\Activity\Stream($observer_id, $contexts, $filter);
+```
+
+Wobei der Parameter `$observer_id` die Nutzer-ID des Betrachters ist, `$contexts` ein Kontext oder ein Array der zu betrachtenden Aktivitätskontexte (z.B. eine Veranstaltung, ein Institut oder ein Nutzer) und `$filter` ein entsprechendes Filter-Objekt ist.
+
+
+Möchte man diesen ausgeben:
+
+
+```php
+foreach($stream->asArray() as $key => $activity) {
+ echo $activity;
+}
+```
+
+#### Filter
+Um den Zugriff auf bestimmte Aktivitäten oder Zeiräume zu fokussieren, können entsprechenden Filter-Objekte definiert werden. Filter-Objekte bestehen aus einem Start-, Endpunkt und einem Aktivitätstyp, welcher einen entsprechenden Activity-Provider beschreibt.
+
+
+## Activity-Kontexte
+Der Activity-Stream bekommt die Aktivitäten über Activity-Kontexte bereitgestellt. Kontexte beschreiben die Bereiche in Stud.IP, in denen potentielle Aktivitäten generiert werden können.
+
+Beispiele für Kontexte:
+
+Veranstaltungen
+Einrichtungen
+Systemweiter Kontext
+Nutzerbezogener Kontext
+
+Klassenbeschreibung des Activity-Kontext:
+
+```php
+abstract class Context
+{
+ protected
+ $provider;
+
+ /**
+ * return array, listing all active providers in this context
+ *
+ * @return array
+ */
+ abstract protected function getProvider();
+
+ /**
+ * get id denoting the context (user_id, course_id, institute_id, ...)
+ *
+ * @return string
+ */
+ abstract public function getRangeId();
+
+ /**
+ * get type of context (f.e. user, system, course, institute, ...)
+ *
+ * @return string
+ */
+ abstract protected function getContextType();
+
+ /**
+ * get list of activities as array for the current context
+ *
+ * @param string $observer_id
+ * @param \Studip\Activity\Filter $filter
+ *
+ * @return array
+ */
+ public function getActivities($observer_id, Filter $filter)
+ {
+ $providers = $this->filterProvider($this->getProvider(), $filter);
+
+ $activities = Activity::findBySQL('context = ? AND context_id = ? AND mkdate >= ? AND mkdate <= ? ORDER BY mkdate DESC',
+ array($this->getContextType(), $this->getRangeId(), $filter->getStartDate(), $filter->getEndDate()));
+
+ foreach ($activities as $key => $activity) {
+ if (isset($providers[$activity->provider])) { // provider is available
+ $providers[$activity->provider]->getActivityDetails($activity);
+ } else {
+ unset($activities[$key]);
+ }
+ }
+
+ return array_flatten($activities);
+ }
+
+ /**
+ *
+ * @param type $provider
+ */
+ protected function addProvider($provider)
+ {
+ $class_name = 'Studip\Activity\\' . ucfirst($provider) . 'Provider';
+
+ $reflectionClass = new \ReflectionClass($class_name);
+ $this->provider[$provider] = $reflectionClass->newInstanceArgs();
+ }
+
+ /**
+ *
+ * @param type $providers
+ * @param \Studip\Activity\Filter $filter
+ * @return type
+ */
+ protected function filterProvider($providers, Filter $filter)
+ {
+ $filtered_providers = array();
+
+ if (is_null($filter->getType())) {
+ $filtered_providers = $providers;
+ } else {
+ foreach($providers as $provider) {
+ $filtered_class = 'Studip\Activity\\' . ucfirst($filter->getType()) . 'Provider';
+
+ if ($provider instanceof $filtered_class) {
+ $filtered_providers[] = $provider;
+ }
+ }
+ }
+
+ return $filtered_providers;
+
+ }
+}
+```
+
+## Activity-Provider
+Orthogonal zu diesen Kontexten existieren Activity-Provider. Diese aggregieren bestimmte Activities. Beispiele für Activity-Provider:
+
+*Forum
+*Blubber
+*Dateibereich
+*Nachrichten
+*News
+*Teilnehmer
+*Wiki
+*Ablaufplan
+*Liteaturverwaltung
+
+#### Das Activity-Provider-Interface
+
+Möchte man einen neuen Activity-Provider implementieren, muss man das entsprechende Interface berücksichtigen, welches die Methoden `getActivityDetails()` und `getLexicalField()` voraussetzt.
+
+```php
+interface ActivityProvider
+{
+ /**
+ * Fill in the url, route and any lengthy content for the passed activity
+ *
+ * @param Studip\Activity\Activity $activity
+ */
+ public function getActivityDetails(&$activity);
+
+ /**
+ * Human readable name for the current provider to be used in the activity-title
+ */
+ public static function getLexicalField();
+}
+```
+
+Um eine persistente Datenhaltung der Aktivitäten sicherzustellen, wird empfohlen zusätzlich die Methode `postActivity()` zu implementieren, welche unter Verwendung entsprechender Observer aus dem NotificationCenter Daten in die Activity-Tabelle speichert.
+
+#### Die Klasse ActivityObserver
+Die Klasse ActivityObserver sorgt dafür, dass relevante Observer aus dem NotifactionCenter geladen werden. Hier können nach Bedarf weitere Observer für z.B. neue Provider abgelegt werden.
+
+
+## Activities
+
+## Activity-Feed Widget
+
+Das ActivityFeed-Widget wird als Portalplugin implementiert. Neben der Ansicht des Activity-Streams bietet das Widget dem Nutzer auch die Möglichkeit den Stream individuell zu Filtern.
+
+## REST-Route
+Als alternative Zugriffsmöglichkeit werden der Stud.IP REST-API folgende neue Routen hinzugefügt:
+
+```php
+/user/:user_id/activitystream # get - Ausgabe des Streams
+```
+
+Die Route kann durch Angabe weiterer Queryparameter gefiltert werden.
diff --git a/docs/docs/functions/admin-search.md b/docs/docs/functions/admin-search.md
new file mode 100644
index 0000000..cbb62cf
--- /dev/null
+++ b/docs/docs/functions/admin-search.md
@@ -0,0 +1,126 @@
+---
+id: admin-search
+title: Wie erweitere ich die Admin-Suche?
+sidebar_label: Admin-Suche
+---
+
+Ab der 3.1 gibt es einen neuen Admin-Bereich, der auch gerne Admin-MeineVeranstaltungen genannt wird. Ab der 3.2 und 3.3 gibt es darin neue Möglichkeiten, die Admin-Suche mit Plugins zu erweitern. Das soll den Administratoren für alle Speziallplugins Möglichkeiten bieten, ohne dass sie dabei ihren gewohnten Arbeitsplatz (den Admin-Bereich in Stud.IP) verlassen müssen.
+
+
+
+## Neue Filter in der Sidebar
+
+Dies ist der einfache Teil. Man muss einen Filter in die Sidebar bekommen und die Sidebar kennen wir ja gut. Nur den Zeitpunkt sollte man beachten. Der Konstruktor des Plugins ist der falsche Zeitpunkt. Stattdessen muss sich das Plugin registrieren für eine Notification (des NotificationCenters). Aber immerhin das Registrieren kann es im Konstruktor machen. Und zwar in etwa so:
+
+```php
+if ((stripos($_SERVER['REQUEST_URI'], 'dispatch.php/admin/courses') !== false)) {
+ NotificationCenter::addObserver(
+ $this,
+ 'addLectureshipFilterToSidebar',
+ 'SidebarWillRender'
+ );
+}
+```
+
+
+Und dazu gehört noch eine Methode des Plugins, die "addLectureshipFilterToSidebar" heißt. Sie kann so aussehen:
+
+```php
+public function addLectureshipFilterToSidebar()
+{
+ $widget = new OptionsWidget();
+ $widget->setTitle(_('Lehrauftragsfilter'));
+ $widget->addCheckbox(
+ _('Nur mit Lehrauftrag'),
+ $GLOBALS['user']->cfg->getValue('LECTURESHIP_FILTER'),
+ PluginEngine::getURL($this, [], 'toggle_lectureship_filter')
+ );
+ Sidebar::Get()->insertWidget($widget, 'editmode', 'filter_lectureships');
+}
+```
+
+Letztlich kann man die Sidebar zu dem Zeitpunkt beliebig manipulieren. Für die Filter der Sidebar ist es üblich, je ein eigenes Formular zu definieren, das auf eine besondere Action verweist, in der dann ein globaler Parameter (hier `$GLOBALS['user']->cfg->getValue("LECTURESHIP_FILTER")` ) der UserConfig verändert wird.
+
+Mein Beispiel-Plugin hat also folgerichtig noch eine Action:
+
+```php
+public function toggle_lectureship_filter_action()
+ {
+ $oldvalue = (bool) $GLOBALS['user']->cfg->getValue("LECTURESHIP_FILTER");
+ $GLOBALS['user']->cfg->store("LECTURESHIP_FILTER", $oldvalue ? 0 : 1);
+ header("Location: ".URLHelper::getURL("dispatch.php/admin/courses"));
+ }
+```
+
+Diese Action ändert nur den UserConfig-Eintrag und schickt den Nutzer gleich zurück zur Admin-Seite.
+
+Dies ist jetzt ein Beispiel mit einer Checkbox, die nur zwei Zustände hat. Aber auf dieselbe Weise könnte man Freitextfelder oder Select-Boxen oder jedes andere komplexe Formular unter bringen. Wichtig ist der Ablauf: Sidebar-Formular einbauen, Nutzer klickt drin rum, Seite lädt sich neu und in einer speziellen Action wird der globale Parameter verändert.
+
+## Die AdminCourseFilter-Klasse
+
+Jetzt muss dieser Filter noch angewendet werden. Klicken kann man ihn schon, aber er muss noch tatsächlich die Ergebnisse verändern können. Dazu wird die Klasse AdminCourseFilter wichtig. Sie regelt den gesamten Query, mit dem die Veranstaltungen zusammen gesucht werden. Kurz bevor der Query ausgeführt wird, wird eine Notification des NotificationCenters angeschmissen, die "AdminCourseFilterWillQuery" heißt. Ein Plugin, das einen Filter anwenden möchte, sollte sich im Konstruktor (Achtung, es sollte ein SystemPlugin sein) entsprechend registrieren. Das geht so:
+
+```php
+NotificationCenter::addObserver($this, "addMyFilter", "AdminCourseFilterWillQuery");
+```
+
+Zudem sollte das Plugin eine Methode besitzen, die "addMyFilter" heißt. Das ist natürlich nur ein Beispielname und sollte abgeändert werden. So könnte diese Methode aussehen:
+
+```php
+public function addLectureshipFilter($event, $filter)
+{
+ if ($GLOBALS['user']->cfg->getValue("LECTURESHIP_FILTER")) {
+ $filter->settings['query']['joins']['lehrauftrag'] = [
+ 'join' => "INNER JOIN",
+ 'table' => "lectureship",
+ 'on' => "seminare.Seminar_id = lehrauftrag.seminar_id"
+ ]
+ }
+}
+```
+
+Auf die Details gehen wir gleich ein. Wichtig ist erst einmal, die Methode bekommt als zweiten Parameter ein $filter Objekt vom Typ "AdminCourseFilter" und kann dieses Objekt beliebig modifizieren. Die hier angegebene Methode modifiziert nur, wenn ein bestimmter UserConfig-Parameter gesetzt ist. Das ist der Parameter, der in der Sidebar gesetzt wird.
+
+### Modifizieren des AdminCourseFilter Objektes
+
+Wie modifiziert man dieses AdminCourseFilter Objekt jetzt eigentlich? Am Ende kommt eine SQL-Query heraus. Dass man das Objekt modifizieren möchte, bedeutet im Grunde, dass man alle Teile des Queries modifizieren will, jeden JOIN und jedes SELECT und natürlich auch das WHERE. Dazu hat das $filter Objekt ein öffentliches Attribut $filter->settings, in dem der ganze Query in einem Array gespeichert wird. Alle Einträge des Arrays sind assoziative Einträge von wiederum assoziativen Arrays. Auf diese Weise kann man neue Einträge hinzufügen, etwa um eine WHERE-Klausel einzubauen, aber man kann auch bestehende Einträge löschen oder ändern.
+
+```php
+$filter->settings = [
+ 'query' => [],
+ 'parameter' => []
+];
+```
+
+In dem Query-teil steht alles drin, um ein Prepared-SQL-Statement zu erzeugen. Im Parameter-Teil stehen dann die notwendigen Parameter drin, die per `execute` eingesetzt werden.
+
+```php
+$filter->settings['query'] = array();
+```
+
+## Weitere Administrationsmodi
+
+In dem Admin-Bereich gibt es in der Sidebar eine Auswahlbox "Aktionsbereich-Auswahl", mit der man einstellen kann, was genau man mit den gefilterten Veranstaltungen machen möchte. Man kann sagen, man will die Grunddaten bearbeiten oder Veranstaltungen archivieren. Aber Plugins können sich hier auch einklinken und einen eigenen Aktionsbereich definieren.
+
+Dazu muss ein Plugin das Interface AdminCourseAction implementieren ( `class LehrauftragPlugin extends StudIPPlugin implements SystemPlugin, **AdminCourseAction**` ).
+
+Dieses Interface besteht aus drei Methoden, die implementiert werden müssen. Dazu muss man verstehen, was der Adminbereich im Aktionsbereich macht. Der Adminbereich ist ja erst einmal eine große Liste von Veranstaltungen mit einem Aktionsbereich rechts in der Zeile der Veranstaltung. Da kann ein Button drin stehen oder eine Checkbox.
+
+Im Falle der Checkboxen braucht es allerdings noch einen Button über und unter den Veranstaltungen, mit dem der ganze Bereich als ein Formular abgeschickt wird. Da stellt sich noch die Frage, wohin das Formular abgeschickt wird?
+
+Also die erste Methode des Interface "AdminCourseAction" ist vermutlich "`public function useMultimode()`", die nur zurückgibt, ob man die Buttons oben und unten braucht. Falls sie nicht gebraucht werden, gibt die Methode `false` zurück, ansonsten `true`. Alternativ kann auch ein String übergeben werden, der gewissermaßen der Text des Buttons ist. Sowas wie "Veranstaltungen archivieren" oder so.
+
+Die zweite Methode ist "`public function getAdminActionURL()`", mit der man definiert, an welche URL das Formular verschickt werden soll.
+
+Die dritte ist die interessanteste Methode, weil sie den Aktionsbereich am Ende tatsächlich definiert. Sie lautet "`public function getAdminCourseActionTemplate($course_id, $values = null)`". Die $course_id sollte klar sein. $values als Parameter gibt an, was die Klasse AdminCourseFilter für die Veranstaltung gefunden hat. Dieser Parameter ist also ein assoziatives Array mit verschiedenen Daten der Veranstaltung. Der Name sollte darin auftauchen, vielleicht auch die Lehrenden. Theoretisch ist dieses Array auch erweiterbar, wie oben zu sehen ist. Damit könnte man sich Einzelabfragen sparen.
+
+Die Methode `getAdminCourseActionTemplate` nimmt nun die Parameter und gibt ein Objekt vom Typ Flexi_Template (oder null) zurück. Dieses Template ist der Aktionsbereich, der als HTML-Schnipsel innerhalb einer Tabellenzelle dargestellt wird. Er kann einen Button ebenso beinhalten wie eine Checkbox oder sogar beides oder ganz andere komplexe Formularfelder.
+
+Den Rest übernimmt die Schnittstelle. Sie speichert zum Beispiel selbst, welcher Aktionsbereich gewählt wurde, sodass ein Admin im richtigen Bereich bleibt.
+
+## Hinzufügen von Spalten zu der Tabelle (ab 4.1)
+
+Plugins haben auch die Möglichkeit, die angezeigte Tabelle um weitere Spalten zu erweitern. Das kann nützlich sein, wenn ein Evaluationsbeauftragter zum Beispiel in einer Spalte sehen möchte, wer die Evaluationsdaten das letzte Mal bearbeitet hat. Dazu kann ein Plugin ein weiteres Plugin-Interface implementieren, das `AdminCourseContents` lautet. Dieses Interface erwartet vom Plugin zwei Methoden.
+
+* `adminAvailableContents()`: Das Plugin liefert hier zurück, welche weiteren Spalten überhaupt möglich sind, in der Tabelle anzuzeigen. Rückgabewert ist ein assoziatives Array, wobei der Index ein interner Name ist und der Wert der sichtbare Name mit allen möglichen Umlauten.
+* `adminAreaGetCourseContent($course, $index)`: Dabei ist der `$index` exakt der Index, den `adminAvailableContents` zurückgeliefert hat. `$course` ist dabei ein Objekt der Klasse `Course` mit der betreffenden Veranstaltung. Rückgabe der Funktion ist entweder ein String oder ein Objekt vom Typ `Flexi_Template`. So ist man flexibel für alles. Wenn man möchte, dass die Spalte auch in der CSV-Datei beim Export sinnvoll auftaucht (was möglich ist), sollte man auf Schnickschnack wie klickbare Buttons im Template verzichtet.
diff --git a/docs/docs/functions/assets.md b/docs/docs/functions/assets.md
new file mode 100644
index 0000000..5573f3f
--- /dev/null
+++ b/docs/docs/functions/assets.md
@@ -0,0 +1,52 @@
+---
+id: assets
+title: Assets
+sidebar_label: Assets
+---
+
+### Einleitung
+
+Alle Bilder, JavaScript- und Stylesheet-Dateien von Stud.IP liegen in einem gemeinsamen Verzeichnis `public/assets` (siehe zum Beispiel: https://gitlab.studip.de/studip/studip/-/blob/main/public/assets)
+
+Ausserdem exisitiert die Konfigurationsvariable `$GLOBALS['ASSETS_URL']`, die eine URI zu diesem Verzeichnis enthält. Standardmässig verweist diese auf das relativ zur jeweiligen Stud.IP-URI gelegene Verzeichnis `assets`.
+
+Beispiel: Ein Stud.IP liegt unter `https://www.meinstudip.de`, dann liegt das Assets-Verzeichnis standardmässig unter `https://www.meinstudip.de/assets`.
+
+Zweck dieser Übung ist, statische Inhalte von anderen Servern ausliefern zu können, um so die eigentlichen Webserver zu entlasten. Ein speziell eingerichteter Assets-Webserver ist viel effizienter in der Auslieferung der statischen Inhalte, als das der normale Webserver könnte.
+
+### Einrichten eines speziellen Assets-Webservers
+
+Vorraussetzung ist ein bereits vorhandener Webserver, der als Assets-Webserver dienen kann. In dieser Hinsicht ist [lighttpd](http://lighttpd.net) sehr zu empfehlen. Kopieren Sie nun einfach das komplette `assets`-Verzeichnis in Ihren Webbereich und notieren Sie sich die URI für dieses Verzeichnis. In Ihrer Stud.IP-Installation öffnen Sie die Konfigurationsdatei `config/config_local.inc.php` und suchen Sie dort nach dem Text `$ASSETS_URL = $ABSOLUTE_URI_STUDIP . 'assets/';`, den Sie dann in die oben notierte URI ändern müssen. **Achten Sie darauf, dass die `$ASSETS_URL` mit einemn Slash enden muss.
+
+### Verwendung der Klasse Assets
+
+Um Bilder, JavaScripts usw., die sich im Assets-Verzeichnis befinden, im HTML-Markup ansprechen zu können, bestünde selbstverständlich die Möglichkeit, direkt die globale Variable `$ASSETS_URL` zu verwenden. Einfacher geht es aber mit der Klasse `Assets`, zudem bietet die Klasse einige Vorteile bei der dynamischen Auslieferung von Grafik-Assets zB. für Retina Displays.
+
+**Für die Einbindung von Icons wird seit Stud.IP v3.4 nicht mehr die Klasse `Assets`, sondern die gesonderte Icon-API verwendet.**
+
+Die Verwendung der Klasse soll hier kurz dargestellt werden.
+
+`echo Assets::img('blank.gif');` gibt einen kompletten Image-Tag aus:
+
+```php
+<img alt="Blank" src="assets/images/blank.gif" />
+```
+
+Will man das `alt`-Attribut ändern oder weitere Attribute hinzufügen, kann man einfach als zweiten Parameter ein Array von Attribut => Attributwerten hinzufügen:
+
+```php
+echo Assets::img('blank.gif', array('alt' => 'nothing here', 'class' => 'some_class'));
+<img alt="nothing here" class="some_class" src="assets/images/blank.gif" />
+```
+
+## Retina-Auflösungen
+
+Die Retina-Klasse kümmert sich automatisch um das Einsetzen von Grafiken in Retina-Auflösung, wenn der Nutzer bei der Anmeldung an einem System mit einer entsprechende Auflösung arbeitet (dieses Verhalten ist ab der Version 2.5 vollständig implementiert). Bei allen Grafiken muss per Parameter darauf hingewiesen werden, dass die entsprechende Grafik auch in einer Retina-Version vorliegt.
+
+Will man nun Grafiken auch in Retina-Auflösung bereitstellen und die Assets-Klasse automatisch die korrekte Größe bzw. Grafikdatei auswählen lassen, so sind folgende Vorraussetzungen zu erfüllen:
+
+- Zunächst muss eine Grafik in doppelter Auflösung (oder anders ausgedrückt in doppelter Größe in X und Y-Dimension) erstellt werden.
+- Die Grafik muss im gleichen Verzeichnis wie die Originaldatei mit dem Zusatz "@2x" (vor der Dateiendung und dem Punkt) abgelegt werden. Zu `header_logo.png` gesellt sich somit zB. `header_logo@2x.png`.
+- Beim Aufruf muss der Parameter "@2x" gesetzt sein. Erst dann sucht die Assets-Klasse nach einer entsprechend größeren Grafikdatei (es gibt keine automatische Suche nach einer Retina-Datei).
+
+*Ein bisschen Hintergrund*: Retina-Grafiken werden auf vielen Smartphones oder Tablets mit hochauflösenden Displays verwendet. Auch die ersten Notebooks mit doppelter Auflösung sind erhältlich. Seit der Version 2.4 prüft Stud.IP beim Login die Pixel-Ratio des Ausgabegeräts und legt diese in der Session ab (der Wert heisst "'devicePixelRatio"). Wenn dieser Wert 2 ist, nimmt Stud.IP für die Dauer der Session eine Retina-Auflösung an. Die Grafiken werden - sofern "@2x" gesetzt ist, in der doppelten Auflösung geladen, aber weiterhin mit der Pixel-Ratio 1 angezeigt. Eine Grafik mit 44*44 wird also aus einer 88*88 Datei geladen, ihr aber die Größe 44*44 mitgegeben. Die aktuellen Browser entscheiden dann, wie sie mit der größeren Pixelanzahl umgehen. Damit aber diese Grafiken nicht auch auf nicht-Retina-Displays geladen und skaliert werden müssen, müssen oben genannte Bedingungen erfüllt werden. Auf Bildschirmen mit geringer Auflösung wird die Grafik dann trotzdem herunterskaliert, falls irrtümlich Retina angenommen wurde.
diff --git a/docs/docs/functions/baumstrukturen.md b/docs/docs/functions/baumstrukturen.md
new file mode 100644
index 0000000..7160a3e
--- /dev/null
+++ b/docs/docs/functions/baumstrukturen.md
@@ -0,0 +1,58 @@
+---
+id: baumstrukturen
+title: Baumstrukturen
+sidebar_label: Baumstrukturen
+---
+
+Mit Stud.IP 5.4 wurden die alten Implementierungen für die Darstellung und Verwaltung der Studienbereiche und Einrichtungshierarchie neu implementiert und eine generische Lösung für die Abbildung von Baumstrukturen geschaffen.
+
+## PHP
+Grundlage ist das neue Interface `StudipTreeNode`. Dieses bietet generische Methoden zum Zugriff auf Bäume:
+- `static getNode($id)` liefert den Knoten mit der angegebenen ID
+- `hasChildNodes()` zeigt an, ob der aktuelle Knoten Kindknoten hat
+- `getChildNodes()` liefert die Kindknoten des aktuellen Knotens
+- `static getCourseNodes($course_id)` liefert die Knoten, denen die angegebene Veranstaltung zugeordnet ist.
+- getter-Methoden für ID, Name, Beschreibung, Bild/Icon
+- `countCourses($semester_id, $semclass, $withChildren)` zählt die Veranstaltungen, die diesem Knoten (oder je nach der Einstellung `withChildren` auch den Unterknoten) zugeordnet sind, gefiltert nach Semester und Kategorie
+- `getCourses($semester_id, $semclass, $searchterm, $withChildren)` liefert die Veranstaltungen, die diesem Knoten (oder je nach der Einstellung `withChildren` auch den Unterknoten) zugeordnet sind, gefiltert nach Semester, Kategorie und/oder Suchbegriff
+- `getAncestors` liefert eine Liste aller "Vorfahren" in der Hierarchie, in der Form `{ id, name }`
+
+Implementierende Klassen sind aktuell `StudipStudyArea` und `RangeTreeNode`.
+
+## Vue
+Zentrale Komponente ist hier `StudipTree`. Ein Baum kann auf verschiedene Arten dargestellt werden, dafür gibt es weitere Vue-Komponenten:
+- `StudipTreeTable` zeigt die Ebenen des Baums analog zum Dateibereich als Tabelle an
+- `StudipTreeList` zeigt die Ebenen des Baums analog zur alten Veranstaltungssuche als Liste von Kacheln an
+- `StudipTreeNode` zeigt den Baum als aufklappbare Hierarchie
+
+`StudipTree` ist vielfältig konfigurierbar, um die Ausgabe zu steuern:
+- `viewType` definiert die Art der Darstellung, entweder 'table', 'list' oder 'tree'
+- `startId` ID des Startknotens zur Anzeige (die IDs sind von der Form 'Klassenname_ID')
+- `title` Anzuzeigender Titel für den Baum
+- `openNodes` Liste bereits offener Knoten (nur sinnvoll für Anzeige als Baum)
+- `openLevels` Allgemeine Zahl geöffneter Ebenen (nur sinnvoll für Anzeige als Baum)
+- `withChildren` Unterebenen anzeigen?
+- `withCourses` Zugeordnete Veranstaltungen anzeigen?
+- `semester` Voreingestelltes Semester im Sidebarfilter
+- `semClass` Voreingestellte Kategorie im Sidebarfilter
+- `breadcrumbIcon` Icon für die Brotkrumennavigation
+- `itemIcon` Icon für die Unterebenenen in der Tabellenanzeige (aktuell fest auf "Ordner" verdrahtet)
+- `withSearch` Zeige eine Veranstaltungssuche an?
+- `withExport` Zeige einen Exportlink für vorhandene Veranstaltungen/Suchergebnisse?
+- `editUrl` URL zum Bearbeitungsformular eines existierenden Knotens
+- `createUrl` URL zum Anlegedialog eines neuen Knotens
+- `deleteUrl` URL zum Löschen eines existierenden Knotens
+- `showStructureAsNavigation` Zeige zusätzlich zur regulären Darstellung noch eine Baumstruktur als Inhaltsverzeichnis? (Nur sinnvoll bei Tabellen- oder Listendarstellung)
+- `assignable` Sind die Knoten zuweisbar?
+
+## JSON-API
+Es wurden neue Routen zum Konstruieren der Baumstruktur bereitgestellt:
+- `/tree-node/{id}` Hole den Knoten mit der angegebenen ID
+- `/tree-node/{id}/children` Hole die Kindknoten der angegebenen ID
+- `/tree-node/{id}/courseinfo` Hole Informationen über die Anzahl der zugeordneten Veranstaltungen
+- `/tree-node/{id}/courses` Hole die zugeordneten Veranstaltungen
+- `/tree-node/course/pathinfo/{classname}/{id}` Hole die Pfade im Baum, denen die angegebene Veranstaltung zugeordnet ist
+- `/tree-node/course/details/{id}` Hole Informationen der angegebenen Veranstaltung, die einem Baumknoten zugeordnet ist (Lehrende, Semester, Termine)
+
+## Verwendung
+An jedem Element mit dem Attribut `data-studip-tree` wird automatisch eine Baumanzeige erzeugt, wenn dort entsprechend die Vue-Komponente `StudipTree` vorhanden ist.
diff --git a/docs/docs/functions/cache.md b/docs/docs/functions/cache.md
new file mode 100644
index 0000000..7556c77
--- /dev/null
+++ b/docs/docs/functions/cache.md
@@ -0,0 +1,112 @@
+---
+id: cache
+title: Stud.IP-Cache
+sidebar_label: Stud.IP-Cache
+---
+
+## Stud.IP-Cache
+
+Stud.IP enthält ein minimales Framework, um beliebige Daten zu cachen. In dem Verzeichnis [lib/classes/cache](https://gitlab.studip.de/studip/studip/-/tree/main/lib/classes/cache) sind die folgenden Klassen und Interfaces enthalten:
+
+* Klasse `Studip\Cache\Factory` (Stud.IP < 6.0 = `StudipCacheFactory`)
+* Interface `Studip\Cache\Cache` (Stud.IP < 6.0 = `StudipCache`)
+* Klasse `Studip\Cache\DbCache` (Stud.IP < 6.0 = `StudipDbCache`)
+* Klasse `Studip\Cache\FileCache` (Stud.IP < 6.0 = `StudipFileCache`)
+* Klasse `Studip\Cache\MemoryCache` (Stud.IP < 6.0 = `StudipMemoryCache`)
+* Klasse `Studip\Cache\MemcachedCache` (Stud.IP < 6.0 = `StudipMemcachedCache`)
+* Klasse `Studip\Cache\RedisCache` (Stud.IP < 6.0 = `StudipRedisCache`)
+* Klasse `Studip\Cache\Proxy` (Stud.IP < 6.0 = `StudipCacheProxy`)
+
+Seit Version 1.11 ist das Caching fester Bestandteil (StudipFileCache) und standardmässig aktiviert. Ab der Version 4.1 ist `StudipDbCache` die Voreinstellung und es gibt kleine Erweiterungen an der Cache-API (s.u.).
+
+### Studip\Cache\Factory
+
+Die Aufgabe der `Studip\Cache\Factory` ist das zur Verfügung stellen eines Stud.IP-weiten Caches. Um die Singleton-Instanz zu erhalten, muss lediglich `Studip\Cache\Factory::getCache()` aufgerufen werden.
+
+### Studip\Cache\Cache
+
+Das Interface `Studip\Cache\Cache` definiert die Operationen einer Cache-Instanz:
+
+| Aktion | Beschreibung |
+| ---- | ---- |
+| `expire($key)` | entfernt ein Schlüssel-Wert-Paar aus dem Cache |
+| `flush()` | leert den Cache, d.h. entfernt alle Werte aus dem Cache **[ab Stud.IP 4.1]** |
+| `read($key)` | liest den Wert zu einem Schlüssel aus dem Cache oder gibt im Falle eines cache miss `FALSE` zurück |
+| `write($key, $value, $expire = 43200)` | legt unter einem Schlüssel für eine gegebene Zeit (in Sekunden, Default: 12 Stunden) einen Wert ab. |
+
+
+Der Cache kann dazu verwendet werden, über mehrere HTTP-Requests hinweg einen möglicherweise aufwendig zu berechnenden Wert zu speichern. Der Cache garantiert nicht, dass ein Schlüssel-Wert-Paar für die gesamte `expire`-Dauer vorgehalten wird. Er garantiert lediglich, dass das Schlüssel-Wert-Paar nach Ablauf nicht mehr zurückgeliefert wird. Bis einschließlich der Stud.IP Version 4.0 müssen die Werte den Typ `string` haben, ab Version 4.1 können auch Werte beliebigen Typs im Cache abgelegt werden (solange die Werte serialisierbar sind). Der Schlüssel ist immer ein String (maximal 255 Zeichen) und sollte nur ASCII-Zeichen enthalten.
+
+Der Lebenszyklus eines Schlüssel-Wert-Eintrags in den Cache sieht so aus:
+
+* Der Eintrag wird mit `#write` eingetragen.
+* Nun kann der Eintrag mit `#read` und dem Schlüssel ausgelesen werden.
+* Der Eintrag scheidet unter folgenden Bedingungen aus:
+** Die Lebensdauer ist überschritten worden.
+** Es wurde explizit `#expire` oder `#flush` aufgerufen.
+** Der Cache entfernt den Wert, z.B. aus Platzmangel.
+
+In der Regel sollte man nur Resultate referenziell transparenter Funktionen [cachen](http://de.wikipedia.org/wiki/Memoisierung), andernfalls muss man sich selbst um die Invalidierung des gespeicherten Wertes kümmern, wenn das alte Resultat ungültig wird. Dazu stehen drei Möglichkeiten zur Verfügung:
+
+* Man verwendet die eingestellte Lebensdauer.
+* Man entfernt die Einträge per `#expire`.
+* Man wählt den Schlüssel geschickt.
+
+Beispiel: Es soll eine Liste der Geburststagskinder angezeigt werden. Da die Berechnung aufwendig ist, soll das Ergebnis gespeichert werden. Ab Mitternacht ist die Liste neu zu berechnen. Unter Berücksichtigung der obigen drei Punkte kann man also folgendermaßen vorgehen:
+
+* Beim Eintragen in den Cache rechnet man aus, wieviele Sekunden noch bis Mitternacht vergehen und stellt die Lebensdauer entsprechend ein.
+* Man liest nicht nur die Liste aus dem Cache aus, sondern noch einen zweiten Wert, der angibt, für welchen Tag diese Liste ist. Falls man sich an einem anderen Tag befindet, berechnet man neu.
+* Man nimmt den aktuellen Tag im Monat in den Schlüssel des Cache-Eintrags auf: "birthdays/22". Am 22. wird die Liste ausgelesen; nach Mitternacht (der Schlüssel ist ja dann "birthdays/23") findet man keinen Eintrag und berechnet neu.
+
+Ganz offensichtlich ist (in diesem Fall) die letzte Möglichkeit die eleganteste.
+
+#### Konvention
+Der Schlüssel eines Cache-Eintrags wird durch Vorwärtsschrägstriche "/" in Namensräume aufgeteilt. Stud.IP-Kerndateien sollten "core/XYZ/argument1/argument2/usw" erzeugen. Stud.IP-Plugins sollten dementsprechend `<plugin>/birthday/22` verwenden. Auf diese Weise sollte es zu keinen Kollisionen kommen.
+
+#### Achtung
+Bis zur Version 4.0 musste der Wert eines Cache-Eintrags ein String sein. Arrays oder Objekte müssen daher (de)serialisiert werden.
+
+#### Funktionsbeispiele
+```php
+//Beispiel beim schwarzenBrettPlugin
+
+// Konstante in der Klasse festlegen
+const ARTIKEL_CACHE_KEY = 'plugins/SchwarzesBrettPlugin/artikel/';
+
+//Beispielfunktion
+private function getArtikel($thema_id)
+{
+ // Cache-Objekt erzeugen
+ $cache = Studip\Cache\Factory::getCache();
+ // Daten aus dem Cache holen
+ $ret = unserialize($cache->read(self::ARTIKEL_CACHE_KEY.$thema_id));
+
+ // Wenn der Cache leer ist, Daten aus der Datenbank holen
+ if (empty($ret)) {
+ $ret = DBManager::get()->query("SELECT ...")->fetchAll(PDO::FETCH_COLUMN);
+ // Daten in den Cache schreiben
+ $cache->write(self::ARTIKEL_CACHE_KEY.$thema_id, serialize($ret));
+ }
+ return $ret;
+}
+```
+
+### Studip\Cache\DbCache
+
+Da es sich bei `Studip\Cache\Cache` nur um ein Interface handelt, muss eine konkrete Implementation zur Verfügung gestellt werden. Die Standardimplementierung des Caches ist seit Stud.IP 4.1 die Klasse `Studip\Cache\DbCache`, die alle Werte in der Tabelle `cache` der Stud.IP-Datenbank ablegt.
+
+### Studip\Cache\FileCache
+
+Die Standardimplementierung des Cache war bis Stud.IP 4.0 die Klasse `Studip\Cache\FileCache`, die alle Werte in Dateien im Stud.IP-Temp Verzeichnis ablegt.
+
+### Studip\Cache\MemoryCache
+
+Wird der Cache in der Konfiguration ausgeschaltet (oder wird die CLI-Umgebung verwendet), steht kein Cache zur Verfügung. In diesem Fall wird der `Studip\Cache\MemoryCache` verwendet, der dann zwar von der Factory zurückgeliefert wird und auch entsprechend und gültig antwortet, allerdings die Werte nur im Speicher des aktuellen Prozesses vorhält.
+
+## Studip\Cache\MemcachedCache bzw. Studip\Cache\RedisCache
+
+Diese beiden Caches nutzen Memcached bzw. Redis für die Speicherung des Caches. Sie können in der Cacheverwaltung im Adminbereich aktiviert und konfiguriert werden.
+
+### Studip\Cache\Proxy
+
+Der Proxy dient dazu, dass Änderungen am Cache auch in den tatsächlichen Cache übertragen werden, wenn man sich in einem Umfeld befindet, indem dieser gerade nicht aktiv ist. Dies ist bspw. auf der Kommandozeile der Fall und wirkt sich somit auf Cronjobs aus.
diff --git a/docs/docs/functions/cli-plugin-manager.md b/docs/docs/functions/cli-plugin-manager.md
new file mode 100644
index 0000000..4270cfe
--- /dev/null
+++ b/docs/docs/functions/cli-plugin-manager.md
@@ -0,0 +1,104 @@
+---
+id: cli-plugin-manager
+title: Pluginmanager auf der Kommandozeile
+sidebar_label: Pluginmanager CLI
+---
+
+# Pluginmanager auf der Kommandozeile
+
+| 📌 **Achtung** |
+|--|
+| Das Kommandozeilen-Tool `cli/plugin_manager` findet bis **Stud.IP v5.0** Verwendung und wird dann durch das allgemeinere Tool [`cli/studip`](CLI) abgelöst. |
+
+Wenn man mal nicht die Möglichkeit die Plugins über die Oberfläche zu verwalten oder einfach mal ein bereits an der passenden Stelle abgelegtes Plugin Stud.IP unterzuschieben, dann kommt einem dieses Tool gerade recht. Es liegt im Ordner `cli` und kann von jeder Stelle aufgerufen unter dem Namen `plugin_manager` aufgerufen werden.
+
+Es bietet folgende Befehle:
+
+## Installieren einer Plugin-Datei
+
+```shell
+./cli/plugin_manager install pfad/zur/plugin.zip
+```
+
+Installiert das Plugin in der angegebenen ZIP-Datei. Diese Aktion ist analog zu dem installieren eines Plugins über die Oberfläche.
+
+## Registrieren eines Plugins
+
+```shell
+./cli/plugin_manager register public/plugins_packages/origin/plugin
+```
+
+Wenn das Plugin bereits an der korrekten Stellen im Verzeichnisbaum von Stud.IP liegt, so kann man es hiermit im System registrieren. Es werden dabei etwaige Installations-SQL Dateien ausgeführt und fehlende Migrationen nachgezogen
+
+## Entfernen der Registrierung eines Plugins
+
+```shell
+./cli/plugin_manager unregister PluginName
+```
+
+Entfernt das Plugin mit dem angegeben Namen aus dem System und führt alle "down"-Migrationen des Plugins aus.
+
+## Pluginmigrationen ausführen
+
+```shell
+./cli/plugin_manager migrate PluginName [-l] [-v] [-t *zahl]
+
+-l Listet nur auf was getan werden soll, migriert aber nicht.
+-v Schaltet den "verbose"-Modus ein. Es werden zusätzliche Informationen über die durchgeführten Migrationen angezeigt.
+-t zahl - Erlaubt einem die Zielmigration. Eine 0 führt zum zurücknehmen aller Migrationen, ansonsten wird zu der entsprechenden Migration (hoch oder runter) hinmigriert. Gibt man diesen Parameter nicht an, so wird automatisch bis zur aktuellsten Migration hochmigriert.
+```
+
+Erlaubt es, Plugin-Migrationen auszuführen. Verhält sich dabei analog zum [Stud.IP-CLI-Migrator](Migrations#toc7).
+
+## Plugin einschalten
+
+```shell
+./cli/plugin_manager activate PluginName
+```
+
+Schaltet das angegebene Plugin ein
+
+## Plugin ausschalten
+
+```shell
+./cli/plugin_manager deactivate PluginName
+```
+
+Schaltet das angegebene Plugin aus
+
+## Plugininfo anzeigen
+
+```shell
+./cli/plugin_manager info PluginName
+```
+
+Zeigt Informationen zu einem einzelnen oder allen installierten Plugins an. Die Informationen werden aus der Datenbank gelesen, wenn das Plugin im Dateisystem nicht gefunden wurde, ist \[class_exists\] => 0 gesetzt.
+
+```shell
+[id] => 142
+[name] => Forum
+[class] => CoreForum
+[path] => core/Forum
+[type] => ForumModule,StandardPlugin,StudipModule
+[enabled] => 1
+[position] => 50
+[depends] => 0
+[core] => 1
+[class_exists] => 1
+```
+
+## Alle verfügbaren Plugins auflisten
+
+```shell
+./cli/plugin_manager scan
+```
+
+Listet alle Plugins auf, die im Dateisystem vorhanden sind, aber nicht in der Datenbank registriert sind.
+
+```shell
+[pluginclassname] => Achievements
+[pluginname] => Achievements
+[origin] => tgloeggl
+[version] => 0.1.2
+[path] => /path/to/trunk/public/plugins_packages/tgloeggl/Achievements
+```
diff --git a/docs/docs/functions/cli.md b/docs/docs/functions/cli.md
new file mode 100644
index 0000000..3e4bf87
--- /dev/null
+++ b/docs/docs/functions/cli.md
@@ -0,0 +1,43 @@
+---
+title: Stud.IP auf der Kommandozeile
+sidebar_label: CLI
+---
+| :pushpin: **Achtung** |
+|-----------------------|
+| Bis einschließlich **Stud.IP v5.0** finden sich alle Kommandozeilen-Tools als eigenständige Skripte im Ordner `/cli`. Das komplexeste, alte Tool hatte seine eigene Dokumentation: [cli/plugin_manager (alt)](CLIPluginManager) |
+
+Ab **Stud.IP v5.1** sind alle Kommandozeilen-Tools auf die Verwendung der Bibliothek [symfony/console](https://symfony.com/doc/current/components/console.html) umgestellt worden.
+
+## Verwendung
+
+Die Liste aller vorhandenen (Sub-)Kommandos kann mit Hilfe dieses Kommandos angezeigt werden:
+
+```shell
+$ cli/studip
+```
+
+Daraufhin wird eine Liste ähnlich wie diese hier ausgegeben:
+
+![CLI Help](../assets/06e2031cec109992fa2df034e5c65642/CLIHelp.png)
+
+Die dort aufgeführten Kommandos können als erstes Argument zu `cli/studip` verwendet werden. Welche zusätzlichen Optionen zur Verfügung stehen, kann mit Hilfe von `--help` abgerufen werden.
+
+```shell
+$ cli/studip migrate --help
+```
+
+
+![CLIHelpOnCommands](../assets/c36105bd58464c5eb00e596123af30c8/CLIHelpOnCommands.png)
+
+Jedes der Kommandos bietet ausführlich genug Hilfe zu seiner
+Verwendung an.
+
+## Neue Kommandos hinzufügen
+
+Um weitere Kommandos hinzuzufügen, sind drei Schritte notwendig:
+
+- [Dokumentation von `symfony/console`](https://symfony.com/doc/current/components/console.html) lesen
+- Neue Klasse in `/cli/commands/{Gruppe}` anlegen. Es gibt schon einige Gruppen. Wenn keine der vorhandenen Gruppen passt, kann eine neue angelegt werden. Gruppen sind gleichzeitig auch Bestandteil des Namespaces der Klasse.
+- Klasse in `cli/studip` registrieren. Die neu erstellte Klasse muss dem `$commands`-Array in der Datei `/cli/studip` hinzugefügt werden.
+
+Danach steht das neue Kommando bei Aufruf von `cli/studip` zur Verfügung.
diff --git a/docs/docs/functions/course-wizard.md b/docs/docs/functions/course-wizard.md
new file mode 100644
index 0000000..6c66ac9
--- /dev/null
+++ b/docs/docs/functions/course-wizard.md
@@ -0,0 +1,98 @@
+---
+title: Anlegeassistent Veranstaltungen
+---
+
+### Architektur
+
+Der Anlegeassistent besteht aus zwei Teilen.
+* Der Trails-Controller (`app/controllers/course/wizard.php`) übernimmt die Ablaufsteuerung des Assistenten: welche Schritte im Assistenten gibt es, in welcher Reihenfolge werden sie durchlaufen, und wie werden die Daten weitergegeben.
+* Die einzelnen Schritte sind jeweils in Klassen gekapselt, die das Interface `CourseWizardStep` implementieren.
+
+### Der Controller
+
+Im Trails-Controller wird die Abfolge der einzelnen Schritte und das Zwischenspeichern der eingegebenen Daten übernommen. Wichtige Actions des Controllers sind:
+
+**`index_action()`**
+
+DIese Action dient nur als Einstiegspunkt für neue Veranstaltungen.
+
+**`step_action($number=0, $temp_id=*)`**
+
+Hier findet die Anzeige eines einzelnen Schrittes statt. Parameter `$number` gibt hierbei an, welche Nummer der Schritt in der Abfolge hat. Der Parameter `$temp_id` dient dazu, die eingebenen Daten der einzelnen Schritte auch über die Schritte hinweg verfügbar zu halten. Ist die `$temp_id` nicht angegeben, wird eine neue generiert. Alle eingebenen Daten werden in der Session gespeichert, in der Form:
+```php
+$_SESSION[$temp_id] = [
+ '<StepClassname1>' = [
+ 'name1' => 'value1',
+ ...
+ ],
+ '<StepClassname2>' = [
+ 'name2' => 'value2',
+ 'name3' => ['value3', 'value4'],
+ ...
+ ],
+ ...
+];
+```
+Durch die bei jedem Schritt mitgeschleifte `$temp_id` ist sichergestellt, dass auch in mehreren Tabs parallel neue Veranstaltungen angelegt werden können. Die hier vergebene `$temp_id` ist nicht die spätere `seminar_id` der angelegten Veranstaltung.
+
+**`process_action($step_number, $temp_id)`**
+
+Diese Action wird nach dem Abschicken des Formulars aufgerufen, alle eingebenen Daten werden in die Session geschrieben.
+
+Wurde im Assistenten auf "Zurück" geklickt, so wird einfach der vorhergehende Schritt aufgerufen.
+
+Wurde "Weiter" geklickt, so werden alle eingegebenen Daten an die Schrittklasse übergeben. Schlägt diese fehl, wird der aktuelle Schritt nochmals angezeigt, durch die Validierung generierte Fehlermeldungen werden ausgegeben. Die Generierung der Fehlermeldungen (`PageLayout::postMessage` u.ä.) wird in der Schrittklasse erledigt.
+
+Nicht alle registrierten Schritte sind immer notwendig, z.B. müssen nur dann Studienbereiche angegeben werden, wenn ein entsprechender Veranstaltungstyp gewählt wurde. Bei erfolgreicher Datenvalidierung ermittelt der Controller, welcher der eingetragenen Schritte der nächste erforderliche ist, und leitet zu dessen Anzeige weiter.
+
+Sind bereits alle notwendigen Schritte durchlaufen, wird eine kurze Meldung angezeigt, dass alle nötigen Daten eingegeben wurden und die Veranstaltung nun angelegt werden kann. Wird dies bestätigt, legt der Controller eine neue, völlig leere, unsichtbare Veranstaltung an, damit eine ID generiert wird. Diese Veranstaltung wird dann sequenziell an jede einzelne der aufgerufenen Schrittklassen übergeben, damit die Daten der Veranstaltung mit den gespeicherten Werten befüllt werden können. Wie die genau zu geschehen hat, weiß jede Schrittklasse selbst. Das modifizierte `Course`-Objekt wird von der Klasse zurückgegeben und dann an den nächsten Schritt weitergereicht.
+
+Nach Abschluss erfolgt eine Weiterleitung auf den Verwaltungsbereich der angelegten Veranstaltung oder auf "Meine Veranstaltungen", falls der Anlegeassistent durch Admins oder Roots von dort im Dialog aufgerufen wurde.
+
+**`ajax_action()`**
+
+Manche Schritte möchten vielleicht Daten per AJAX nachladen. Die Klasse selbst hat jedoch keine URL, die als Endpunkt für solche AJAX-Calls dienen kann. Daher gibt es diese Action, die per Request folgende Parameter übergeben bekommt:
+* `step`: Nummer des aktuellen Schritts; dient dazu, die zugehörige Klasse zu ermitteln
+* `method`: in der Zielklasse aufzurufende Methode, die die gewünschten Daten liefert
+* `parameter`: dieses Array enthält alle für den Aufruf der Methode nötigen Parameter in der korrekten Reihenfolge
+
+Um z.B. die Methode `getFoo($param1, $param2)` in der Klasse des Schritts 3 aufrufen zu können, lautet die korrekte (GET-)URL zum Controller:
+
+`<studip>/dispatch.php/course/wizard/ajax?step=3&method=getFoo&parameter[]=<param1>&parameter[]=<param2>`
+
+**`forward_action($step_number, $temp_id)`**
+
+Diese Action dient einfach nur dazu, direkt per Request Daten an die Schrittklasse durchreichen zu können. Das ist vor allem dann wichtig, wenn kein JavaScript zur Verfügung steht. So wird z.B. das Öffnen eines Studienbereichsknotens realisiert, indem einfach der Parameter `open_node=<id`> an die forward_action gehängt wird.
+
+**`copy_action($id)`**
+
+Wird zum Kopieren der Veranstaltung mit der ID `$id` aufgerufen. Dort wird die zu kopierende Veranstaltung sequenziell an alle registrierten Schritte übergeben, deren `copy`-Methode die nötigen Daten für jeden Schritt extrahiert und so das in der Session gespeicherte Datenarray für den Assistenten befüllt.
+
+### Das Interface `CourseWizardStep`
+
+Jede Klasse, die in den Assistenten eingebunden werden soll, muss das Interface `CourseWizardStep` implementieren. Dazu gehören folgende Methoden:
+
+**`getStepTemplate($values, $stepnumber, $temp_id)`**
+
+Hier lädt der Schritt das Flexi-Template zur Anzeige und befüllt dessen Daten mit den Werten aus dem `$values`-Array. Wie weiter oben beschrieben, enthält dieses Array alle eingegebenen Werte aller Schrittklassen, will ein Schritt also nur die Werte haben, die von der eigenen Klasse kommen, so stehen diese in `$values[__CLASS__]`.
+
+Die im Kern enthaltenen Templates liegen standardmäßig unter `app/views/course/wizard/steps`, das kann aber jeder Schritt für sich selbst durch die Instanziierung einer entsprechenden Flexi_TemplateFactory selbst regeln.
+
+**`isRequired($values)`**
+
+Anhand der übergebenen, bereits eingegeben Werte bestimmt der Schritt, ob er für den Anlegeprozess erforderlich ist. Bekanntestes Beispiel dafür ist der `StudyAreasWizardStep` (Zuordnung von Studienbereichen), der nur angezeigt werden muss, wenn in einem vorhergenden Schritt ein entsprechender Veranstaltungstyp gewählt wurde.
+
+**`alterValues($values)`**
+Neben den Standardbuttons für "Weiter" und "Zurück", die durch den Assistenten navigieren, kann es im Eingabeformular eines Schritts auch weitere Buttons geben, die das Formular absenden. Hierdurch ausgelöste Aktionen werden vom Controller direkt an diese Methode der Schrittklasse weitergereicht, wo sie verarbeitet werden können. So kann man z.B. für den Fall ohne Javascript Buttons einbauen, die bestimmte Werte übernehmen. Auch direkte Aufrufe der `forward`-Methode im Controller, die ein Request generieren, ohne das Formular abschicken, werden hierhin übergeben.
+
+**`validate($values)`**
+
+Beim Fortfahren zum nächsten Schritt im Assistenten werden die bisher eingegebenen Daten an diese Klassenmethode übergeben, um auf Vollständigkeit und Plausibilität überprüft zu werden. Im Fehlerfall müssen hier auch entsprechende Meldungen erzeugt werden, z.B. über `PageLayout::postMessage`. Wie bei `getStepTemplate` werden auch hier alle Werte aller Schrittklassen übergeben, die eigenen Werte sind also über `$values[__CLASS__]` verfügbar.
+
+**`storeValues($course, $values)`**
+
+Diese Methode wird direkt vor dem Anlegen der Veranstaltung aufgerufen. Das übergebene Course-Objekt repräsentiert die anzulegende Veranstaltung, die mit den übergebenen Werten weiter befüllt werden kann. Auch hier gilt: $values enthält alle Werte aller Schrittklassen.
+
+**`copy($course, $values)`**
+
+Mit dieser Methode kann das $values-Array mit Werten befüllt werden, die z.B. aus dem übergebenen Course-Objekt (= die zu kopierende Veranstaltung) stammen können.
diff --git a/docs/docs/functions/coursesets.md b/docs/docs/functions/coursesets.md
new file mode 100644
index 0000000..ff2b868
--- /dev/null
+++ b/docs/docs/functions/coursesets.md
@@ -0,0 +1,159 @@
+---
+id: coursesets
+title: Anmeldesets und -regeln
+sidebar_label: Anmeldesets und -regeln
+---
+
+## Anmeldesets und -regeln
+
+### Konzept
+Ein Anmeldeset stellt einen Rahmen für Veranstaltungen dar, die gemeinsame Regeln zur Anmeldung besitzen. Mit Stud.IP 3.0 werden initial schon einige verschiedene Regeln mitgeliefert, hier soll jedoch unter anderem beschrieben werden, wie man selbst eine solche Regel implementieren kann.
+
+
+
+### Aufbau einer Anmelderegel
+Alle Anmelderegeln liegen im Ordner `lib/admissionrules`. Pro Regeltyp gibt es dort einen Ordner, in dem typischerweise die Klassendefinition der Regel liegt (`Regeltyp.class.php`, SQL-Anweisungen, die bei (De-)Installation der Regel ausgeführt werden müssen und templates zur Konfiguration und zur Kurzanzeige der Regel.
+
+Im Folgenden soll das Beispiel `NightAdmission` entwickelt werden, eine Regel, die eine Anmeldung nur zwischen 22 und 6 Uhr zulässt.
+
+#### Speichern und Laden der Daten
+Die Regel vom Typ `NightAdmission` sollen alle in einer eigenen Tabelle `nightadmissions` in der Datenbank gespeichert werden. Da dieser Regeltyp neben dem standardmäßig vorhandenen Infotext keine weiteren Attribute besitzt, sieht diese Tabelle so aus:
+
+```sql
+CREATE TABLE `nightadmissions` (
+ `rule_id` VARCHAR(32) NOT NULL,
+ `message` TEXT NOT NULL,
+ `mkdate` INT NOT NULL,
+ `chdate` INT NOT NULL,
+ PRIMARY KEY (`rule_id`);
+```
+
+Wird dieser Regeltyp komplett aus dem System entfernt, so reicht folgende SQL-Anweisung zum Aufräumen:
+
+```sql
+DROP TABLE `nightadmissions`;
+DELETE FROM `courseset_rule` WHERE `type`='NightAdmission';
+```
+
+#### Regeldefinition
+Wir legen eine Datei `NightAdmission.class.php` an, die von der bereits vorhandenen Klasse `AdmissionRule` erbt. Da wir nur die aktuelle Uhrzeit berücksichtigen müssen, braucht diese Klasse keine eigenen, weiteren Attribute. Wir definieren nur, mit welchen anderen Anmelderegeltypen diese Regel kombinierbar ist (nämlich alle Standardregeln außer zeitgesteuerter und komplett gesperrter Anmeldung).
+
+Einige Standardmethoden müssen wir ebenfalls implementieren, um das Laden und Speichern in eigene Tabellen zu realisieren.
+
+```php
+<?php
+class NightAdmission extends AdmissionRule {
+
+ /**
+ * Standardkonstruktor
+ */
+ public function __construct($ruleId=*, $courseSetId = *)
+ {
+ parent::__construct($ruleId, $courseSetId);
+ $this->default_message = _("Sie können sich nur nachts zwischen 22 und 6 Uhr anmelden.");
+ if ($ruleId) {
+ // Regel bereits vorhanden, lade Daten.
+ $this->load();
+ } else {
+ // Erzeuge neue ID.
+ $this->id = $this->generateId('nightadmissions');
+ }
+ return $this;
+ }
+
+ /**
+ * Lösche aktuelle Regel aus der Datenbank.
+ */
+ public function delete() {
+ parent::delete();
+ $stmt = DBManager::get()->prepare("DELETE FROM `nightadmissions` WHERE `rule_id`=?");
+ $stmt->execute(array($this->id));
+ }
+
+ /**
+ * Beschreibungstext für diesen Regeltyp, wird angezeigt, wenn eine neue
+ * Regel zu einem Anmeldeset hinzugefügt werden soll.
+ */
+ public static function getDescription() {
+ return _("Diese Regel erlaubt die Anmeldung nur nachts zwischen 22 und 6 Uhr.");
+ }
+
+ /**
+ * Name für diesen Regeltyp, wird angezeigt, wenn eine neue Regel zu einem
+ * Anmeldeset hinzugefügt werden soll.
+ */
+ public static function getName() {
+ return _("Nächtliche Anmeldung");
+ }
+
+ /**
+ * Holt das Template zur Anzeige der Konfiuration dieser Regel
+ * (configuration.php, hinterlegt im Unterordner templates). Für unser
+ * Beispiel brauchen wir nur das Standardtemplate, da es nichts eigenes*
+ * für diesen Regeltyp zu konfigurieren gibt.
+ */
+ public function getTemplate() {
+ $tpl = $GLOBALS['template_factory']->open('admission/rules/configure');
+ $tpl->set_attribute('rule', $this);
+ return $tpl->render();
+ }
+
+ /**
+ * Lädt die Regel aus der Datenbank.
+ */
+ public function load() {
+ $rule = DBManager::get()->fetch("SELECT * FROM `nightadmissions` WHERE `rule_id`=? LIMIT 1", array($this->id));
+ $this->message = $rule['message'];
+ return $this;
+ }
+
+ /**
+ * Diese Funktion überprüft, ob sich der gegebene Benutzer zur gegebenen
+ * Veranstaltung anmelden darf, ob die Regel also greift.
+ * Zurückgegeben wird eine Fehlermeldung, falls die Anmeldung nicht
+ * möglich ist.
+ */
+ public function ruleApplies($userId, $courseId) {
+ $failed = array();
+ $now = mktime();
+ // Zeit zwischen 6 und 22 Uhr => keine Anmeldung erlaubt.
+ if (date('H', $now) < 22 && date('H', $now) >= 6) {
+ $failed[] = $this->default_message;
+ }
+ return $failed;
+ }
+
+ /**
+ * Speichert die aktuelle Regel in der Datenbank.
+ */
+ public function store() {
+ $stmt = DBManager::get()->prepare("INSERT INTO `nightadmissions`
+ (`rule_id`, `message`, `mkdate`, `chdate`)
+ VALUES
+ (:id, :message, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())
+ ON DUPLICATE KEY
+ UPDATE `message`=VALUES(`message`), `chdate`=VALUES(`chdate`)");
+ $stmt->execute(array('id' => $this->id, 'message' => $this->message));
+ return $this;
+ }
+
+}
+```
+
+Am wichtigsten sind die beiden Methoden `ruleApplies` und `getTemplate`.
+
+Erstere Methode spezifiert das Verhalten der Regel, also wann und unter welchen Voraussetzungen überhaupt eine Anmeldung erfolgreich sein kann. Hier können im Prinzip beliebige Datenbankabfragen oder sonstige anderen Funktionen aufgerufen werden.
+
+Das Template definiert die GUI zur Konfiguration der jeweiligen Regel. Standardmäßig wird nur ein Textfeld angeboten, das einen Text aufnehmen kann, der vor der Anmeldung auf der Veranstaltungsseite erscheint. Will man hier weitere Werte, Checkboxen oder anderes einstellbar machen, muss man selbst ein [Flexi-Template](FlexiTemplates) dafür schreiben.
+
+Daneben kann es noch ein Info-Template geben, das nur zur Anzeige der Regel in normalem Prosatext dient.
+
+### Zusammenfassung
+Um diese Beispielregel in Stud.IP zu installieren, reicht es, in `lib/admissionrules` einen Ordner `nightadmissions` zu erstellen, dort die obige Klasse `Nightadmission.class.php` hineinzukopieren und die nötigen SQL-Anweisungen auszuführen. Da kein eigenes Template benötigt wird, ist hier auch kein Unterordner `templates` von Nöten. In der **globalen Konfiguration** unter **Anmelderegeln ** kann die Regel dann aktiviert werden. An gleicher Stelle muss dann unter **Regelkompatibilität** eingestellt werden, mit welchen vorhandenen Regelndie neue Regel kombinierbar ist.
+
+
+
+## Verteilungsalgorithmus
+Der Algorithmus, der die Plätze der Veranstaltungen eines Anmeldesets verteilt, kann ebenfalls frei selbst implementiert werden. Standardmäßig wird bereits ein Algorithmus mitgeliefert.
+
+Zum Anlegen eines neuen Algorithmus reicht es, das vorhandene Interface `AdmissionAlgorithm` zu implementieren, dabei handelt es sich im Prinzip nur um eine Methode `run()`, die den Algorithmus ausführt.
diff --git a/docs/docs/functions/cronjobs.md b/docs/docs/functions/cronjobs.md
new file mode 100644
index 0000000..b8367c6
--- /dev/null
+++ b/docs/docs/functions/cronjobs.md
@@ -0,0 +1,102 @@
+---
+id: cronjobs
+title: Cronjobs
+sidebar_label: Cronjobs
+---
+
+Um Cronjobs nutzen zu können, müssen sie entsprechend installiert werden. Die Installationsanleitung gibt dazu [ausführlich Auskunft](http://docs.studip.de/admin/Admins/Installationsanleitung#toc23).
+
+Zu beachten sind bei der Cronjob-Erstellung mindestens zwei Probleme:
+
+* Das Cronjob-Skript auf CLI-Ebene wird ggf. unter einer anderen Nutzerkennung ausgeführt als die Skripte über die Weboberfläche. Dies kann zu Seiteneffekten bei bestimmten Operationen wie zum Beispiel bei Dateien führen.
+* Nicht jedes Cache-Modul ist auch auf CLI-Ebene verfügbar. Dies führt dazu, dass Klassen sich unterschiedlich verhalten können, wenn sie über die Weboberfläche oder über einen Cronjob ausgeführt werden. Sollten sich Inhalte scheinbar nicht ändern, prüfen Sie den Zustand in der Datenbank und falls dort Unterschiede zur Weboberfläche festzustellen sind, ist bestimmt ein Cache im Spiel. Prüfen Sie in diesem Zusammenhang auch die Einstellung `CACHING_ENABLE` in der Datei `cli/studip_cli_env.inc.php`. Eventuell kann der Cache auf CLI-Ebene aktiviert werden, aber das hängt von dem verwendeten Tool ab.
+
+Seit Stud.IP Version 3.3 hat sich das im letzten Punkt angesprochene Verhalten verbessert, da das Caching über einen speziellen Cache-Proxy konsistenter im CLI-Modus und im Webmodus arbeitet. Es können aus dem CLI-Modus Werte gelöscht werden. Das Auslesen aus dem Cache ist allerdings immer noch nicht unbedingt gewährleistet.
+
+### Anatomie
+
+#### Vollständiges Beispiel
+
+```php
+<?php
+class ExampleCronjob extends CronJob
+{
+ public static function getName()
+ {
+ return _('Beispiel');
+ }
+
+ public static function getDescription()
+ {
+ return _('Beispiel Cronjob, der nichts macht');
+ }
+
+ public static function getParameters()
+ {
+ return [
+ 'verbose' => [
+ 'type' => 'boolean',
+ 'default' => false,
+ 'status' => 'optional',
+ 'description' => _('Sollen Ausgaben erzeugt werden'),
+ ],
+ ];
+ }
+
+ public function setUp()
+ {
+ }
+
+ public function execute($last_result, $parameters = [])
+ {
+ do_something();
+ }
+
+ public function tearDown()
+ {
+ }
+}
+```
+
+
+#### Grundlagen
+
+#### `setup() / teardown()`
+
+
+#### `execute()`
+
+
+#### Parameter
+
+Gültige Parameter-Typen sind die folgenden:
+
+| Parameter | Beschreibung |
+| ---- | ---- |
+| `boolean` | |
+| `string` | (1-zeiliger Text) |
+| `text` | (mehrzeiliger Text) |
+| `integer` | `select` (die gültigen Werte werden in dem separaten Feld `values` definiert|
+
+
+Über das Feld `default` kann ein Standardwert angegeben werden, während über das Feld `status` angegeben wird, ob der Parameter zwingend erforderlich (`mandatory`) oder optional (`optional`) ist. In dem Feld `description` kann eine Beschreibung für den Parameter mitgegeben werden, der bei der Konfiguration des Cronjobs angezeigt wird.
+
+### Registrieren/Installation eines Cronjobs
+
+```php
+<?php
+require_once '../classes/example_cronjob.php'; // Contains ExampleCronjob
+
+class CronjobMigration extends Migration
+{
+ public function up()
+ {
+ ExampleCronjob::register()->schedulePeriodic(59, 23)->activate();
+ }
+
+ public function down()
+ {
+ ExampleCronjob::unregister();
+ }
+}
+```
diff --git a/docs/docs/functions/css-z-indizes.md b/docs/docs/functions/css-z-indizes.md
new file mode 100644
index 0000000..755b70c
--- /dev/null
+++ b/docs/docs/functions/css-z-indizes.md
@@ -0,0 +1,24 @@
+---
+id: css-z-indizes
+title: CSS-Z-Indizes
+sidebar_label: CSS-Z-Indizes
+---
+
+
+### Liste der verwendeten Z-Indizes (Stand: 09. Juli 2014)
+
+| Index | Beschreibung |
+| ------ | ------ |
+| 1 | Zeile unterhalb des Headers (#barBottomContainer) |
+| 2 | Sidebar / Overdiv-Bibliothek (inzwischen obsolet?) |
+| 3 | Header-Navigation (#barTopMenu) |
+| 998 | WYSIWYG Editor: "Dropzone" |
+| 999 | WYSIWYG Editor: Toolbar |
+| 1000 | Helpbar / Widget-Hinzufügen-Lasche auf der Startseite |
+| 1001 | Dialoge mittels studip-dialog.js |
+| 1003 | Stud.IP Tooltips |
+| 9999 | jQuery UI - Tooltips / Dialoge mittels createQuestion() |
+| 10000 | Fixierter Header (nach Scrollen) / Tour Overlay |
+| 20000 | Tour: Tooltips |
+| 20001 | Tour: Kontroll-Box |
+| 99999 | Autocomplete - Ergebnisse |
diff --git a/docs/docs/functions/deputy.md b/docs/docs/functions/deputy.md
new file mode 100644
index 0000000..fabfd4d
--- /dev/null
+++ b/docs/docs/functions/deputy.md
@@ -0,0 +1,112 @@
+---
+id: deputy
+title: Vertretung
+sidebar_label: Vertretung
+---
+
+# Allgemeines
+
+Ab Stud.IP 2.0 steht Funktionalität zur Verfügung, mit der Vertretungen von Personen definiert werden können, die z.B. innerhalb von Veranstaltungen volle Rechte von DozentInnen haben, aber nicht nach außen sichtbar sind. Darüber hinaus können auch Standardvertretungen einer Person definiert werden, die dann automatisch als Vertretung zu Veranstaltungen hinzugefügt werden, wenn die jeweilige Person DozentIn ist. Weiter können solche Standardvertretungen das Recht erhalten, die Profilseite der Person in vollem Umfang zu bearbeiten, deren Standardvertretung sie sind.
+
+Zugehörige Funktionen sind in der Datei `lib/deputies_functions.inc.php` definiert.
+
+Damit die Vertretungsfunktionalität aktiv ist, muss in der globalen Konfiguration die Variable `DEPUTIES_ENABLE` gesetzt sein. Damit Personen ihre Standardvertretungen definieren können, muss zusätzlich `DEPUTIES_DEFAULTENTRY_ENABLE` aktiviert sein, um auch Rechte zur Bearbeitung des eigenen Profils an die Vertretung übergeben zu können, muss `DEPUTIES_EDIT_ABOUT_ENABLE` aktiviert sein.
+
+# Abfrage und Verändern von Vertretungen
+
+### Frage bestehende Vertretungen ab
+
+Zur Abfrage der bestehenden Vertretungen zu einer Veranstaltung oder Person gibt es die Methode `getDeputies`, diese bekommt eine Veranstaltungs- oder Personen-ID sowie optional ein Namensformat (wie üblich etwas in der Art "full" oder "full_rev", Default ist "full_rev") und gibt dann ein Array der Vertretungen zurück:
+```php
+// Definiere hier eine Seminar-ID...
+$seminar_id = '9a739ae7fffab0c2347a783cef0f69be';
+// ... und hole die Vertretungen in dieser Veranstaltung
+$deputies = getDeputies($seminar_id);
+```
+führt zu folgendem Ergebnis:
+
+```php
+$deputies = Array
+(
+ [a272fef013c2b9367d1525daeb307c95] => Array
+ (
+ [user_id] => a272fef013c2b9367d1525daeb307c95
+ [username] => tester
+ [Vorname] => Toni
+ [Nachname] => Tester
+ [edit_about] => 0
+ [perms] => tutor
+ [fullname] => Tester, Toni
+ )
+
+)
+```
+Wie ersichtlich ist, wird ein Array zurückgegeben mit den jeweiligen Nutzer-IDs als Schlüssel und folgenden Daten:
+
+* User-ID
+* Username
+* Vorname
+* Nachname
+* Darf die Person die Profilseite des "Chefs" bearbeiten (bei Veranstaltungen natürlich immer 0)
+* globale Rechtestufe der Vertretung
+* Voller Name gemäß dem beim Aufruf angegebenen Format
+
+## Frage ab, von welchen Personen ein User Vertretung ist
+
+Für den umgekehrten Fall, also zu ermitteln, vom wem eine bestimmte Person die Standardvertretung ist, gibt es die Funktion `getDeputyBosses`:
+
+```php
+// Hole alle Personen, von denen User-ID #12345' Vertretung ist:
+$bosses = getDeputyBosses('12345');
+```
+Hier wird (analog zu `getDeputies`) ein Array mit Nutzerdaten zurückgegeben.
+
+## Hinzufügen oder Entfernen von Vertretungen
+
+Zum Verändern der bestehenden Vertretungen existieren die Funktionen `addDeputy`, `deleteDeputy` und `deleteAllDeputies`.
+
+Hier Beispiele zur Verwendung:
+
+```php
+// Füge Benutzer mit ID '12345' zur Veranstaltung mit der ID '67890' als Vertretung hinzu:
+addDeputy('12345', '67890');
+
+// Entferne Benutzer mit ID '12345' als Vertretung aus der
+// Veranstaltung '67890':
+deleteDeputy('12345', '67890');
+
+// Lösche alle Vertretungen aus der Veranstaltung '67890':
+deleteAllDeputies('67890');
+```
+
+Um der Standardvertretung einer Person die Rechte zur Bearbeitung der Profilseite zu geben oder zu entziehen, gibt es die Funktion `setDeputyHomepageRights`:
+
+```php
+// Vertretung mit ID '12345' bekommt das Recht, die
+// Profilseite von User-ID 'abcde' zu bearbeiten:
+setDeputyHomepageRights('12345', 'abcde', 1);
+```
+
+# Abfrage, ob Vertretung
+Über die Methode `isDeputy` kann abgefragt werden, ob eine Person Vertretung in einer Veranstaltung oder von einer bestimmten anderen Person ist. Als Parameter wird die User-ID der abzufragenden Person und die ID der Veranstaltung oder Person angegeben, wo Person 1 Vertretung sein soll. Optional kann noch mit abgefragt werden, ob es sich um eine Vertretung mit Profilbearbeitungsrechten handelt.
+
+```php
+// Ist Person 1 mit ID '12345'...
+$person1_id = '12345';
+// ... Vertretung von Person 2 mit ID 'abcde' ...
+$person2_id = 'abcde';
+// ... , egal ob mit Profilbearbeitungsrechten oder ohne.
+$result = isDeputy('12345', 'abcde');
+
+// Überprüfe zusätzlich, ob Person 1 die Profilseite von
+// Person 2 bearbeiten darf:
+$result = isDeputy('12345', 'abcde', true);
+```
+Analog wird für Veranstaltungen abgefragt, hier darf natürlich nur ohne die Profilbearbeitungsrechte abgefragt werden.
+
+# Sonstige Funktionen
+Über die Funktion `getValidDeputyPerms` kann abgefragt werden, welche Berechtigung eine Person mindestens haben muss, um überhaupt als Vertretung eintragbar zu sein (Höchstberechtigung ist immer 'dozent'). Momentan ist hier fest die Berechtigung 'tutor' implementiert, d.h. nur Personen mit der globalen Berechtigung 'tutor' oder 'dozent' sind erlaubt.
+
+Die Funktion `haveDeputyPerm` überprüft für eine anzugebene User-ID, ob diese Person die nötigen Rechte hat, um als Vertretung eingetragen werden zu können.
+
+In `getMyDeputySeminarsQuery` sind Datenbankanfragen hinterlegt, die dazu dienen, die Veranstaltungen zu finden, in denen der aktuelle User Vertretung ist. Je nach Kontext ist dies für Meine Veranstaltungen, Gruppierungs- und Benachrichtigungseinstellungen sowie den Benachrichtungs-Cronjob von Bedeutung. Die resultierende Datenbankanfrage wird über "UNION" mit der normalen Anfrage der jeweiligen Daten verbunden.
diff --git a/docs/docs/functions/etask-tables.md b/docs/docs/functions/etask-tables.md
new file mode 100644
index 0000000..948700c
--- /dev/null
+++ b/docs/docs/functions/etask-tables.md
@@ -0,0 +1,315 @@
+---
+id: etask-tables
+title: Beschreibung der `etask_*`-Tabellen
+sidebar_label: etask_* - Tabellen
+---
+
+## etask_tasks - Aufgaben
+Ab Stud.IP 4.0 gibt es gemeinsame Tabellen für Aufgaben in Stud.IP, die von verschiedenen Tools (und auch Plugins) verwendet werden können. Da viele Tools spezifische Zusatzinformationen ablegen wollen, gibt es in vielen Tabellen ein zusätzliches Feld `options`, das (fast) beliebige weitere Daten im JSON-Format aufnehmen kann. Für darüber hinaus gehende Anforderungen können natürlich auch weiterhin eigene Tabellen angelegt werden.
+
+Die Tabelle `etask_tasks` enthält die einzelnen Aufgaben. Eine Aufgabe kann dabei in verschiedenen Kontexten verwendet werden, daher stehen z.B. die Informationen zur Bewertung (falls es so etwas gibt) nicht direkt bei der Aufgabe. Der Typ sollte eigentlich ein PHP-Klassenname sein, allerdings gibt es im Kern noch keine definierten Klassen für die Aufgabentypen, daher gibt es da aktuell nur den Typ "`multiple-choice`".
+
+| Attribut | Beschreibung |
+| ---- | ---- |
+| **id** | ID der Aufgabe (Primärschlüssel, Auto-Increment) |
+| **type** | Aufgabentyp (Multiple-Choice, Text, Zuordnung o.ä.), geplant: PHP-Klassenname |
+| **title** | Aufgabentitel (ohne Formatierung) |
+| **description** | Aufgabentext (mit Formatierung) |
+| **task** | Aufgabeninhalte in einer typspezifischen JSON-Repräsentierung (siehe Beispiele unten) |
+| **user_id** | Ersteller der Aufgabe |
+| **mkdate** | Erstellungsdatum |
+| **chdate** | Änderungsdatum |
+| **options** | weitere, nicht typspezifische Daten im JSON-Format (nach Bedarf) |
+
+
+## etask_task_tags - Schlüsselworte für Aufgaben
+
+Falls ein Tool Schlüsselworte zu Aufgaben zuweisen möchte, kann dazu die Tabelle `etask_task_tags` verwendet werden. Jeder Nutzer sieht dabei nur seine eigenen Schlüsselworte.
+
+| Attribut | Beschreibung |
+| ---- | ---- |
+| **task_id** | ID der Aufgabe |
+| **user_id** | Nutzer des Schlüsselworts |
+| **tag** | Schlüsselwort|
+
+
+## etask_tests - Aufgabensammlungen
+
+Aufgaben können zu Aufgabensammlungen zusammengefaßt werden (etwa so wie Dateien in Ordnern). Jede Aufgabensammlungen ist in der Tabelle `etask_test` abgelegt.
+
+| Attribut | Beschreibung |
+| ---- | ---- |
+| **id** | ID der Aufgabensammlung (Primärschlüssel, Auto-Increment) |
+| **title** | Titel der Aufgabensammlung (ohne Formatierung) |
+| **description** | Beschreibung der Aufgabensammlung (mit Formatierung) |
+| **user_id** | Ersteller der Aufgabensammlung |
+| **mkdate** | Erstellungsdatum |
+| **chdate** | Änderungsdatum |
+| **options** | weitere Daten im JSON-Format (nach Bedarf) |
+
+## etask_test_tags - Schlüsselworte für Aufgabensammlungen
+
+Falls ein Tool Schlüsselworte zu Aufgabensammlung zuweisen möchte, kann dazu die Tabelle `etask_test_tags` verwendet werden. Jeder Nutzer sieht dabei nur seine eigenen Schlüsselworte.
+
+| Attribut | Beschreibung |
+| ---- | ---- |
+| **test_id** | ID der Aufgabensammlung |
+| **user_id** | Nutzer des Schlüsselworts |
+| **tag** | Schlüsselwort |
+
+
+## etask_test_tasks - Zuordnung der Aufgaben zu den Aufgabensammlungen
+
+Die Tabelle `etask_test_tasks` speichert die Zuordnung der einzelnen Aufgaben zu den Aufgabensammlungen sowie ggf. weitere an der Zuordnung hängende Informationen wie die Punktzahl.
+
+| Attribut | Beschreibung |
+| ---- | ---- |
+| **test_id** | ID der Aufgabensammlung |
+| **task_id** | ID der Aufgabe |
+| **position** | Position in der Sammlung (numeriert ab 1) |
+| **points** | erreichbare Punkte (optional) |
+| **options** | weitere Daten im JSON-Format (nach Bedarf) |
+
+
+## etask_assignments - gestellte Aufgabensammlungen (Aufgabenblätter)
+
+Eine Aufgabensammlung ist zunächst mal nur eine Menge von Aufgaben ohne Zuordnung. Über ein `etask_assignments` wird die Aufgabensammlung einer Menge von Teilnehmern gestellt (z.B. in Form eines Aufgabenblatts oder eine Abstimmung). Bei einer Einfachzuordnung zu einem Kontext steht die Zuordnung direkt in dieser Tabelle, bei Mehrfachzuordnung in der Tabelle `etask_assignment_ranges`.
+
+| Attribut | Beschreibung |
+| ---- | ---- |
+| **id** | ID des Aufgabenblatts (Primärschlüssel, Auto-Increment) |
+| **test_id** | ID der Aufgabensammlung |
+| **range_type** | Kontext-Typ, aktuell sind definiert: `course`, `global`, `group`, `institute`, `user` |
+| **range_id** | Kontext der Zuordnung, z.B. die ID einer Veranstaltung |
+| **type** | Präsentationsmodus als Text, vom Tool abhängig (nicht vordefiniert) |
+| **start** | Start des Bearbeitungszeitraums (optional) |
+| **end**| Ende des Bearbeitungszeitraums (optional) |
+| **active**| sichtbar/unsichtbar für Teilnehmer, kann z.B. für einen Entwurfsmodus verwendet werden |
+| **options**| weitere Daten im JSON-Format (nach Bedarf) |
+
+## etask_assignment_ranges -Mehrfachzuordnung von Aufgabensammlungen zu Kontexten
+
+Wenn Aufgabensammlungen mehreren Kontexten zugewiesen werden sollen, sind diese in `etask_assignment_ranges` abgelegt.
+
+| Attribut | Beschreibung |
+| ---- | ---- |
+| **id** | ID der Zuordnung (Primärschlüssel, Auto-Increment) |
+| **assignment_id** | ID des Aufgabenblatts |
+| **range_type** | Kontext-Typ, aktuell sind definiert: `course`, `global`, `group`, `institute`, `user` |
+| **range_id** | Kontext der Zuordnung, z.B. die ID einer Veranstaltung |
+| **options** | weitere Daten im JSON-Format (nach Bedarf) |
+
+## etask_assignment_attempts - individueller Lösungsversuch zu einem Aufgabenblatt
+
+In `etask_assignment_attempts` wird pro Teilnehmer abgelegt, ob ein Aufgabenblatt bereits angefangen wurde (und wann), und ggf. auch ein indivduelles Enddatum der Bearbeitung gespeichert. Zusätzlich können auch weitere Informationen je nach Tool gespeichert werden.
+
+| Attribut | Beschreibung |
+| ---- | ---- |
+| **id** | ID des Lösungsversuchs (Primärschlüssel, Auto-Increment) |
+| **assignment_id** | ID des Aufgabenblatts |
+| **user_id** | Teilnehmer |
+| **start** | individueller Start der Bearbeitung (optional) |
+| **end** | individuelles Ende der Bearbeitung (optional) |
+| **options** | weitere Daten im JSON-Format (nach Bedarf) |
+
+## etask_responses - Antworten bzw. Lösungen zu einzelnen Aufgaben
+
+Die Antworten auf die einzelnen Aufgaben werden in der Tabelle `etask_responses` abgelegt. Die Antworten selbst sind natürlich typspezifisch und werden wie die Aufgabeninhalte in einem JSON-Fomat gespeichert.
+
+| Attribut | Beschreibung |
+| ---- | ---- |
+| **id** | ID der Antwort (Primärschlüssel, Auto-Increment) |
+| **assignment_id** | ID des Aufgabenblatts |
+| **task_id** | ID der Aufgabe |
+| **user_id** | Teilnehmer |
+| **response** | Antwort im JSON-Format (abhängig vom Aufgabentyp) |
+| **state** | Status (numerisch, vom Tool zu definieren) |
+| **points** | Bewertung in Punkten (optional) |
+| **feedback** | Feedback zur Antwort (mit Formatierung) |
+| **grader_id** | Nutzer-ID des Feedback-Gebers |
+| **mkdate** | Erstellungsdatum |
+| **chdate** | Änderungsdatum |
+| **options** | weitere Daten im JSON-Format (nach Bedarf) |
+
+
+## JSON-Formate für Aufgabentypen
+
+In diesem Abschnitt ist das JSON-Format der wichtigsten Aufgabentypen definiert. Zu beachten ist, daß der Aufgabentext nicht Teil dieser JSON-Beschreibung ist, sondern direkt in der Spalte `description` in der Tabelle `etask_tasks` gespeichert ist. Für weitere Aufgabentypen müßte die Auflistung hier entsprechend ergänzt werden.
+
+#### Multiple-Choice
+
+Es gibt ein gemeinsames Schema für alle Arten von Aufgaben mit Antwortwahl (mit Ausnahme des Lückentexts).
+
+Beispiel:
+```json
+{
+ "select":"multiple",
+ "optional":false,
+ "answers":[
+ {
+ "text":"Ist der Himmel blau?",
+ "score":1,
+ "feedback":"..."
+ },
+ {
+ "text":"Findet man am Ende des Regenbogens einen Topf voll Gold?",
+ "score":0,
+ "feedback":"..."
+ }
+ ]
+}
+```
+
+| Attribut | Beschreibung |
+| ---- | ---- |
+| **select** | `single` oder `multiple` |
+| **optional** | `true` (Antwort ist optional) oder `false` (Antwort muß gegeben werden) |
+| **answers** | Liste der Antwortmöglichkeiten (ggf. inkl. automatischem Feedback) |
+
+Antwort:
+```json
+[
+ 1, 0
+]
+```
+
+Wenn eine Frage unbeantwortet bleibt (optionale Antwort), wird ein Wert von `-1` eingetragen.
+
+#### Textaufgaben
+
+Für Textaufgaben gibt es ein Schema ähnlich wie bei den Multiple-Choice mit kleinen Erweiterungen. Die vordefinierten Antworten sind hier aber keine Antwortoptionen zur Auswahl, sondern die automatisch auswertbaren Antworten mit entsprechender Bewertung (die Liste kann leer sein, wenn es keine Auswertung gibt).
+
+Beispiel:
+```json
+{
+ "layout":"textarea",
+ "template":"...",
+ "compare":"ignorecase",
+ "answers":[
+ {
+ "text":"Paris",
+ "score":1,
+ "feedback":"..."
+ }
+ ]
+}
+```
+
+| Attribut | Beschreibung |
+| ---- | ---- |
+| **layout** | `input` (einzeilig) oder `textarea` (mehrzeilig) |
+| **template** | initiale Textvorgabe für die Lösung der Teilnehmer |
+| **compare** | Vergleichskriterum für die Auswertung, z.B. `ignorecase` oder `levenshtein` |
+| **answers** | Liste der automatisch ausgewerteten Antworten (ggf. inkl. automatischem Feedback) |
+
+
+Antwort:
+```json
+[
+"foobar"
+]
+```
+
+#### Lückentexte
+
+Beim Lückentext sind die Antworten getrennt vom eigentlichen Lückentext abgelegt, in dem nur die Lücken ausgezeichnet sind. Eine Lücke mit dabei im Text mit `[=[]()=]` markiert. Der Lückentext kann dabei auch formatiert sein (Stud.IP-Formatierung bzw. WYSIWYG-Editor).
+
+Beispiel:
+```json
+{
+ "text":"Die Vase steht []() dem []().",
+ "select":true,
+ "compare":"ignorecase",
+ "answers":[
+ [
+ {
+ "text":"auf",
+ "score":1
+ },
+ {
+ "text":"neben",
+ "score":0,
+ "feedback":"..."
+ },
+ {
+ "text":"unter",
+ "score":0,
+ "feedback":"..."
+ }
+ ],
+ [
+ {
+ "text":"Stuhl",
+ "score":0
+ },
+ {
+ "text":"Tisch",
+ "score":1
+ },
+ {
+ "text":"Teppich",
+ "score":0
+ }
+ ]
+ ]
+}
+```
+
+
+| Attribut | Beschreibung |
+| ---- | ---- |
+| **text** | Text des Lückentexts (mit Formatierung), Lücken sind mit `[=[]()=]` markiert |
+| **select** | `true` (Auswahl aus Liste) oder `false` (Eingabe als Text) |
+| **compare** | Vergleichskriterum für die Auswertung, z.B. `ignorecase` oder `levenshtein` |
+| **answers** | Liste der Antwortmöglichkeiten bzw. der automatisch ausgewerteten Antworten (ggf. inkl. automatischem Feedback) |
+
+
+Antwort;
+```json
+[
+"auf", "Tisch"
+]
+```
+
+#### Zuordnungen
+
+Zuordnungen bestehen aus einer Liste von Gruppen (Kategorien) und einer Liste von Antworten, die diesen Gruppen zugeordnet werden können. Es kann Antworten geben, die unzugeordnet bleiben müssen - diese bekommen die Gruppe mit dem Index "`-1`" zugewiesen.
+
+Beispiel:
+```json
+{
+ "select":"single",
+ "groups":[
+ "Instrument",
+ "Werkzeug"
+ ],
+ "answers":[
+ {
+ "id":42,
+ "text":"Hammer",
+ "group":1
+ },
+ {
+ "id":7,
+ "text":"Geige",
+ "group":0
+ }
+ ]
+}
+```
+
+
+| Attribut | Beschreibung |
+| ---- | ---- |
+| **select** | (Einfachzuordnung) oder `multiple` (Mehrfachzuordnung in jeder Gruppe) |
+| **groups** | Liste der Gruppen für die Zuordnung (numeriert ab 0) |
+| **answers** | Liste der Antwortmöglichkeiten (ggf. inkl. automatischem Feedback) und Gruppenzuordnung |
+
+Antwort:
+```json
+{
+"42": 0, "7": 1
+}
+```
+
+Wenn eine Antwort nicht zugeordnet bleibt, wird ein Wert von "`-1`" eingetragen.
diff --git a/docs/docs/functions/event-logging.md b/docs/docs/functions/event-logging.md
new file mode 100644
index 0000000..9caaea1
--- /dev/null
+++ b/docs/docs/functions/event-logging.md
@@ -0,0 +1,216 @@
+---
+id: event-logging
+title: Event-Logging
+sidebar_label: Event-Logging
+---
+
+
+Ab Version 1.3. wurde ein zentraler Logging-Mechanismus implementiert, der bestimmte Aktionen mit Timestamp und Userkennung festhält.
+
+Mit Version 3.0 wurde das Logging so erweitert, dass beliebige Objekte Log-Einträge generieren können.
+
+## Beispiele:
+
+* "Wir nutzen in einigen Fächern Stud.IP zur verbindlichen Anmeldung zu Veranstaltungen, von denen einige sehr hart umkämpft sind. Beim letzten Mal kam es rund ein halbes Dutzend mal vor, dass Studierende behauptet haben, sie hätten sich angemeldet und vor einer Woche noch einen Platz gehabt, aber jetzt sei der weg. Beim momentanen Stand der Dinge haben wir nichts in der Hand, das nachzuvollziehen."
+* "Die Zuordnung von Veranstaltungen zu Studienbereichen muss im Zweifelsfall von mehreren Kommissionen genehmigt werden. Stud.IP bietet hier (mit Absicht) keine vollständigen Kontrollmechanismen. Deshalb ist es im Zweifelsfall notwendig nachvollziehen zu können, wer eine Zuordnung vorgenommen hat."
+* "Im Zusammenhang mit Raumbuchungen kommt es häufiger zu Fragen, wer einen Raum gebucht hat, oder wann Anfragen geändert wurden."
+* "Wir haben ca. 80 Administratoren und es wäre sehr hilfreich nachvollziehen zu können, wer einen Benutzer angelegt, gelöscht, hochgestuft etc. hat."
+
+## Grundlagen:
+
+* Es gibt ein allgemeines Event-System, das von verschiedenen Stellen des Systems aufgerufen werden kann und für einen Event-Eintrag sorgt
+* Das Event-System ist datenbankbasiert
+* Es gibt eine für Root zugängliche Seite, auf der Log-Events nach verschiedenen Kriterien gesucht und angezeigt werden können
+* Die Anzeige erfolgt in gut lesbarer Weise, trotzdem soll die Speicherung ressourcenschonend geschehen und Informationen strukturiert abgelegt werden können.
+* Um Datenschutzaspekte angemessen berücksichtigen zu können, kann Root das Logging einzelner Events zentral an- und abschalten (seit Version 1.3. vorhanden) und es kann eine automatische Löschung einzelner Events nach einer bestimmten Zeit aktiviert werden.
+
+### Screenshot:
+
+![image](../assets/d853edb1d90bc82e3dc0d69fcb2927b4/image.png)
+
+## Technische Umsetzung
+
+Zugriff auf das Logging erfolgt über die API-Klasse StudipLog, die alle Methoden zur Nutzung des Loggings zur Verfügung stellt.
+
+Jedes beliebigen Objekt kann Ereignisse (die in der Regel das Objekt selbst betreffen) loggen, wenn es die Schnittstelle Loggable implementiert. In der Datenbank registrierte Log-Aktionen können aber prinzipiell an belibiger Stelle genutzt werden.
+
+StudipLog nutzt zwei Modell-Klassen:
+* LogAction: Zur Verwaltung der Beschreibung und Daten von Aktionen. Ein LogAction-Objekt ist also eine Vorlage für ein Ereignis, das geloggt werden kann. Z.B. "Austragen des Nutzers X aus Veranstaltung Y durch Nutzer Z".
+* LogEvent: Das geloggte oder zu loggende Ereignis. Es enthält die Daten eines konkreten Ereignisses und bezieht sich immer auf eine LogAction. Für die o.g. LogAction wäre das die Daten für X,Y und Z als IDs der jeweiligen Objekte.
+
+Die noch verfügbare globale Funktion log_event() sollte nicht mehr verwendet werden.
+
+### Benutzung der Klasse StudipLog
+
+#### Anlegen einer neuen LogAction
+
+#### Implementierung der Schnittstelle Loggable
+
+Es gibt zwei neue Tabellen:
+
+### log_actions
+
+Entählt Beschreibungen und Daten von Aktionen, wie z.B. "Anlegen einer neuen Veranstaltung".
+
+```sql
+CREATE TABLE `log_actions` (
+`action_id` INT( 10 ) NOT NULL AUTO_INCREMENT , // ID
+`name` VARCHAR( 128 ) NOT NULL , // Bezeichner, wird auch im Code verwendet
+`description` VARCHAR(64), // Kurzbeschreibung für Suchinterface
+`info_template` TEXT, // Template für Klartextausgabe
+`active` TINYINT( 1 ) DEFAULT '1' NOT NULL , // derzeit aktiv?
+`expires` INT( 20 ) , // Anzahl Sekunden bis automatischer Löschung
+ PRIMARY KEY ( `action_id` )
+ );
+```
+
+Ein Eintrag sieht dann z.B. so aus:
+
+```sql
+action_id=3,
+name=SEM_VISIBLE
+description="Veranstaltung sichtbar schalten"
+info_template="%user schaltet %sem(%affected) sichtbar."
+active=1
+expires=NULL
+```
+
+Der Info-Template-String kann ein paar Platzhalter enthalten, insgesamt wird daraus bei der Anzeige des Logs der Text generiert, der oben im Scrrenshot zu sehen ist.
+
+expires kann genutzt werden, um Einträge nach einer bestimmten Zeit automatisch löschen zu lassen, z.B. aus datenschutzgründen oder zum Platz sparen. Über ein spezielles Interface (noch nich implementiert) kann das Logging bestimmter Aktionen einfach ein- und ausgeschaltet werden.
+
+Die einzelnen Events werden in einer zweiten Tabelle gespeichert:
+
+### log_events
+
+```sql
+CREATE TABLE `log_events` (
+`event_id` INT( 20 ) UNSIGNED NOT NULL AUTO_INCREMENT , // ID
+`timestamp` INT( 20 ) NOT NULL , // Unix-Timestamp
+`user_id` VARCHAR( 32 ) NOT NULL , // Handelnder Nutzer (Subjekt)
+`action_id` INT( 10 ) NOT NULL , // Handlung (Verb)
+`affected_range_id` VARCHAR( 32 ) , // primär betroffenes Objekt (direktes Objekt)
+`coaffected_range_id` VARCHAR( 32 ) , // sekundär betroffenes Objekt (indirektes Objekt)
+`info` TEXT, // zusätzlicher Informationstext
+`dbg_info` TEXT, // zusätzliche technische Informationen
+ PRIMARY KEY ( `event_id` )
+);
+```
+
+Durch den gewählten Ansatz leistet das Logging zweierlei:
+
+* Lesbare Ausgaben in natürlicher Sprache
+* Volle Durchsuchbarkeit nach betroffenen Objekte (Veranst., Räume, Einrichtungen)
+
+
+## log_event() aufrufen
+
+### Normaler Aufruf
+
+Im Code müssen die Stellen identifiziert werden, an denen eine Aktion ausgelöst wird und meist eine Zeile wie:
+
+```php
+log_event("SEM_CREATE",$sem_id);
+```
+
+eingefügt werden. Sind mehr als zwei Objekte betroffen, kommen die Zusatzinformationen (nicht durchsuchbar) in den Infotext, z.B. die Angabe über die gebuchte Zeit beim Auflösen einer Raumanfrage. In die Debug-Infos können z.B. Details über die Stelle im Code eingebaut werden, von der aus die Aktion ausgeführt wurde, komplette Queries abegelegt werden etc.
+
+```php
+function log_event($action, $affected=NULL, $coaffected=NULL, $info=NULL, $dbg_info=NULL, $user=NULL) {
+...
+}
+```
+
+| Variable | Beschreibung|
+| ---- | ---- |
+|$action|Text-ID des Log-Events|
+|$affected|ID des Objektes, das im Datenbankfeld affected landet (kann je nach Event Veranstaltung, Institut, Nutzer, Ressource, ... sein - da wird nichts gecheckt, sondern einfach eingetragen)|
+|$coaffected|ID des Objektes, das im Datenbankfeld coaffected landet (kann je nach Event Veranstaltung, Institut, Nutzer, Ressource, ... sein - da wird nichts gecheckt, sondern einfach eingetragen)|
+|$info|Freier Text für Feld info|
+|$dgb_info|Freier Text für Debug-Info-Feld|
+|$user|Normalerweise wird die user_id des Handelnden aus der Session übernommen. Hier kann eine abweichende user_id angegeben werden, z.B. für Aktionen, die von "[=%%__SYSTEM__%%=]" ausgeführt werden.|
+
+Die Funktion überprüft, ob das Logging eingeschaltet und das gewünschte Event aktiv ist.
+
+TODO: Beispiele... Bis dahin: Im Code nach log_event(...) suchen ;-)
+
+### Fehler beim Logging
+
+Wird ein Event in der Tabelle log_actions nicht gefunden, wird der Event-Call nicht verworfen, sondern unter dem Event LOG_ERROR mit allen übergebenen Parametern im Info-Text gespeichert:
+
+```php
+log_event("LOG_ERROR",NULL,NULL,NULL,"log_event($action,$affected,$coaffected,$info,$dbg_info) for user $user");
+```
+
+## Eine neue Action hinzufügen
+
+### Eintrag in log_actions
+
+Event-Vorlagen (Actions) werden nicht über die Oberfläche angelegt (würde wenig bringen, da ohnehin Code angefasst werden muss), sondern mit einem SQL-Statement direkt in die Datenbank geschrieben. Die action_id (MD5-Key) kann frei gewählt werden, muss aber eindeutig sein. Es bietet sich an md5(name) zu verwenden.
+
+### Auslösen von Events
+
+Events für die neue Action dann wie oben beschrieben mittels `log_event(<ACTION_NAME>, ...)` ausgelöst werden. Die Semantik der weiteren Parameter ergibt sich aus dem Template in log_actions.
+
+### Abrufen der Events
+
+Die Events stehen anschließend automatisch über das Log-Tool allen Root-Nutzern zur Verfügung (Verwalten globaler Einstellungen -> Tools -> Log). Die neuen Actions sind in der der Auswahlbox links enthalten, angezeigt wird dort der description-Eintrag.
+
+### Beispiel: Action für "E-Mail-Adresse ändern"
+
+Es soll geloggt werden, wer wessen E-Mail-Adresse wann und auf welchen Wert geändert hat.
+
+#### Idee
+
+* WER ändert wird als user_id des Events abgelegt
+* FÜR WEN geändert wurde als affected_id
+* Der NEUE WERT ist kein Stud.IP-Objekt, muss also als Freitext in info abgelegt werden. Dann besser noch: Neuen UND alten Wert ablegen, also z.B. "von tobias.thelen@beispiel.test auf thelen@anderesbeispiel.test".
+
+#### Der Datenbankeintrag für log_actions:
+
+| Tabelle | Wert |
+| ---- | ---- |
+|action_id|21b0b3fc30605876686617a1aec92321|
+|name|CHANGE_EMAIL|
+|description|E-Mail-Adresse ändern|
+|info_template|`%user ändert/setzt E-Mail-Adresse für %user(%affected): %info.`
+|active|1|
+|expires|0|
+
+#### Verwenden des Events:
+
+`log_event("CHANGE_EMAIL",$user_id,*,"von tobias.thelen@beispiel.test auf thelen@anderesbeispiel.test");`
+
+## Standard-Events
+
+```sql
+CREATE TABLE `log_actions` (
+ `action_id` varchar(32) NOT NULL default *,
+ `name` varchar(128) NOT NULL default *,
+ `description` varchar(64) default NULL,
+ `info_template` text,
+ `active` tinyint(1) NOT NULL default '1',
+ `expires` int(20) default NULL,
+ PRIMARY KEY (`action_id`)
+);
+
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('0ee290df95f0547caafa163c4d533991', 'SEM_VISIBLE', 'Veranstaltung sichtbar schalten', '%user schaltet %sem(%affected) sichtbar.', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('a94706b41493e32f8336194262418c01', 'SEM_INVISIBLE', 'Veranstaltung unsichtbar schalten', '%user versteckt %sem(%affected) (unsichtbar).', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('bd2103035a8021942390a78a431ba0c4', 'DUMMY', 'Dummy-Aktion', '%user tut etwas.', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('4490aa3d29644e716440fada68f54032', 'LOG_ERROR', 'Allgemeiner Log-Fehler', 'Allgemeiner Logging-Fehler, Details siehe Debug-Info.', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('f858b05c11f5faa2198a109a783087a8', 'SEM_CREATE', 'Veranstaltung anlegen', '%user legt %sem(%affected) an.', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('5b96f2fe994637253ba0fe4a94ad1b98', 'SEM_ARCHIVE', 'Veranstaltung archivieren', '%user archiviert %info (ID: %affected).', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('bf192518a9c3587129ed2fdb9ea56f73', 'SEM_DELETE_FROM_ARCHIVE', 'Veranstaltung aus Archiv löschen', '%user löscht %info aus dem Archiv (ID: %affected).', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('4869cd69f20d4d7ed4207e027d763a73', 'INST_USER_STATUS', 'Einrichtungsnutzerstatus ändern', '%user ändert Status für %user(%coaffected) in Einrichtung %inst(%affected): %info.', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('6be59dcd70197c59d7bf3bcd3fec616f', 'INST_USER_DEL', 'Benutzer aus Einrichtung löschen', '%user löscht %user(%coaffected) aus Einrichtung %inst(%affected).', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('cf8986a67e67ca273e15fd9230f6e872', 'USER_CHANGE_TITLE', 'Akademische Titel ändern', '%user ändert/setzt akademischen Titel für %user(%affected) - %info.', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('ca216ccdf753f59ba7fd621f7b22f7bd', 'USER_CHANGE_NAME', 'Personennamen ändern', '%user ändert/setzt Name für %user(%affected) - %info.', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('8aad296e52423452fc75cabaf2bee384', 'USER_CHANGE_USERNAME', 'Benutzernamen ändern', '%user ändert/setzt Benutzernamen für %user(%affected): %info.', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('59f3f38c905fded82bbfdf4f04c16729', 'INST_CREATE', 'Einrichtung anlegen', '%user legt Einrichtung %inst(%affected) an.', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('1a1e8c9c3125ea8d2c58c875a41226d6', 'INST_DEL', 'Einrichtung löschen', '%user löscht Einrichtung %info (%affected).', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('d18d750fb2c166e1c425976e8bca96e7', 'USER_CHANGE_EMAIL', 'E-Mail-Adresse ändern', '%user ändert/setzt E-Mail-Adresse für %user(%affected): %info.', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('a92afa63584cc2a62d2dd2996727b2c5', 'USER_CREATE', 'Nutzer anlegen', '%user legt Nutzer %user(%affected) an.', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('e406e407501c8418f752e977182cd782', 'USER_CHANGE_PERMS', 'Globalen Nutzerstatus ändern', '%user ändert/setzt globalen Status von %user(%affected): %info', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('63042706e5cd50924987b9515e1e6cae', 'INST_USER_ADD', 'Benutzer zu Einrichtung hinzufügen', '%user fügt %user(%coaffected) zu Einrichtung %inst(%affected) mit Status %info hinzu.', 1, NULL);
+INSERT INTO `log_actions` (`action_id`, `name`, `description`, `info_template`, `active`, `expires`) VALUES ('4dd6b4101f7bf3bd7fe8374042da95e9', 'USER_NEWPWD', 'Neues Passwort', '%user generiert neues Passwort für %user(%affected)', 1, NULL);
+```
diff --git a/docs/docs/functions/format.md b/docs/docs/functions/format.md
new file mode 100644
index 0000000..7e6a494
--- /dev/null
+++ b/docs/docs/functions/format.md
@@ -0,0 +1,38 @@
+---
+id: format
+title: Formatierungsengine für Stud.IP
+sidebar_label: Formatierungsengine
+---
+
+Vorhandene Funktionen:
+
+| Funktion | Datei | Beschreibung |
+| ---- | ---- | ---- |
+|htmlReady|visual.inc.php|Keine Formatierung, nur simple HTML-Ersetzung|
+|quotes_decode|visual.inc.php|Extrahiert quotes aus Text|
+|quotes_encode|visual.inc.php|Liefert Quote-Code für übergebenen Text|
+|format_help|visual.inc.php|Helper für formatReady, code-Handling|
+|formatReady|visual.inc.php|Formatiert String mit allen Formatierungsmöglichkeiten. Special-modes: trim=yes/no, extern=yes/no, wiki=yes/no, comments=icon/none/full|
+|wikiReady|visual.inc.php|Abkürzung für formatReady, setzt wiki=TRUE|
+|wiki_format|visual.inc.php|Ersetzt Kommentarformatierungen|
+|format_wiki_comment|visual.inc.php|Formatiert einen einzelnen Kommentar entsprechend Parameter|
+|latex |visual.inc.php|Rendert LaTeX-Formel und bindet Bild ein|
+|decodeHTML|visual.inc.php|Ersetzt HTML-Entitities durch entsprechende Character (iso latin-1)|
+|format|visual.inc.php|Wendet reguläre Ausdrücke für Formatierungen an|
+|preg_call_format_text|visual.inc.php|Helper für big/small-Formatierung|
+|preg_call_format_list|visual.inc.php|Helper für Listenformatierung|
+|preg_call_format_table|visual.inc.php|Helper für Tabellenformatierung|
+|preg_call_rss_include|visual.inc.php|RSS-Feed includen|
+|xss_remove|visual.inc.php|Gefährliche HTML-Tags entfernen|
+|kill_format|visual.inc.php|Formatierungen für ASCII entfernen|
+|FixLinks|visual.inc.php|Ersetzt Links durch HTML-Links|
+|preg_call_link|visual.inc.php|Helper für Linkformatierung|
+|idna_link|visual.inc.php|Geniert punycode für Umlaut-URLS|
+|smile|visual.inc.php|Ersetzt Smiley-Ausdrücke|
+|symbol|visual.inc.php|Ersetzt Smiley-Kurzformen|
+
+
+Verarbeitungsreihenfolge:
+
+* Normal: latex -> format -> FixLinks -> rss_include -> smile -> symbol
+* Wiki: latex -> format -> FixLinks -> rss_include -> smile -> symbol -> wiki_format
diff --git a/docs/docs/functions/global-search-module.md b/docs/docs/functions/global-search-module.md
new file mode 100644
index 0000000..e2ca8a7
--- /dev/null
+++ b/docs/docs/functions/global-search-module.md
@@ -0,0 +1,38 @@
+---
+id: global-search-module
+title: Module für die globale Suche
+siebar_label: Globale Suche
+---
+
+# Module für die globale Suche
+Die globale Suche (ab Stud.IP 4.1) hat für jede Suchkategorie eine eigene Klasse, die die nötigen Operationen ausführt, um passende Suchergebnisse zu finden.
+
+Jedes Modul stammt von der abstrakten Oberklasse `GlobalSearchModule` ab und implementiert folgende Methoden:
+
+## Methoden
+* `getName()` liefert einen Anzeigenamen für das Modul zurück. Dieser erscheint in der Konfiguration der Suchmodule und in der kategorisierten Übersicht der Suchergebnisse.
+* `getSQL($search)` generiert die SQL-Anfrage, die ausgeführt werden soll, um die Suchergebnisse aus der Datenbank zu lesen. Hier muss vollständiger SQL-Code zurückgegeben werden, also kein Prepared Statement oder SQL mit Parametern. Um die Suchanfrage performant zu halten, sollte `LIMIT` verwendet werden, um die Anzahl der Ergebnisse zu beschränken. Über die Variable `GLOBALSEARCH_MAX_RESULT_OF_TYPE` aus der globalen Konfiguration wird gesteuert, wie viele Ergebnisse pro Kategorie in der Schnellsuche angezeigt werden. Da jede Kategorie klickbar ist und dann mehr Ergebnisse anzeigt, kann z.B. `LIMIT (4 * Config::get()->GLOBALSEARCH_MAX_RESULT_OF_TYPE)` angegeben sein.
+* `filter($data, $search)` bereit ein einzelnes Suchergebnis zur Weiterverarbeitung vor. Hier wird eine einzelne Datenbankzeile übergeben, aus der dann die für die Darstellung benötigten Attribute erzeugt werden. Die Rückgabe ist ein Array von der Art
+
+```php
+[
+ 'id' => <Stud.IP-ID des Objekts>,
+ 'name' => <Titel/Name des Objekts, am besten über GlobalSearchModule::mark gekennzeichnet>,
+ 'url' => <URL zum Aufruf des Ergebnisses im System, z.B. Profil, Veranstaltungsseite etc.>,
+ 'date' => <Erzeugungs-/Änderungsdatum/Semester>,
+ 'description' => <Ausführlichere Information, Beschreibungstext, Textausschnitte etc, am besten über GlobalSearchModule::mark gekennzeichnet>,
+ 'additional' => <Weitere Daten, wie z.B. Untertitel, Liste von Dozierenden etc.>,
+ 'expand' => <URL zur weiterführenden Suche>,
+ 'img' => <URL eines Bildes/Avatars für dieses Ergebnis>
+]
+```
+* `getSearchURL($searchterm)` liefert die URL einer weiterführenden Suche, z.B. Forensuche innerhalb einer Veranstaltung oder Stud.IP-weite Veranstaltungssuche
+## Markierung des Suchbegriffs
+Um zu kennzeichnen, warum ein Ergebnis überhaupt angezeigt wird, kann die statische Methode `GlobalSearchModule::mark($string, $query, $longtext = false, $filename = true)` verwendet werden, die in einem gegebenen String das Suchwort markiert und ihn ggf. kürzt. Hierfür wird das HTML-Tag `<mark>` verwendet.
+# GlobalSearchFulltext
+Statt der "normalen" SQL-Suche über `LIKE` kann auch in bestimmten MySQL-Versionen die Volltextsuche via `MATCH AGAINST` verwendet werden. Hierfür muss das Interface `GlobalSearchFulltext` implementiert werden. Dieses besitzt drei Methoden:
+* `enable()`: Aktionen, die beim Aktivieren der Volltextsuche dieses Moduls ausgeführt werden sollen, z.B. Erzeugung von nötigen Tabellenindizes
+* `disable()`: Aktionen, die beim Abschalten der Volltextsuche dieses Moduls ausgeführt werden sollen. z.B. Entfernen von Tabellenindizes
+* `getFulltextSearc($search)` generiert analog zu `GlobalSearchModule->getSQL()` die SQL-Anfrage, um die Suchergebnisse aus Datenbank zu holen
+# Aktivierung und Sortierung von Suchmodulen
+Eine neue Suchmodulklasse muss noch unter Admin->Globale Suche aktiviert werden, dort kann auch per Drag & Drop sortiert werden, in welcher Reihenfolge die Module abgefragt bzw. die Suchergebnisse angezeigt werden.
diff --git a/docs/docs/functions/icon.md b/docs/docs/functions/icon.md
new file mode 100644
index 0000000..c570bbf
--- /dev/null
+++ b/docs/docs/functions/icon.md
@@ -0,0 +1,170 @@
+---
+id: icon
+title: Icon
+sidebar_label: Icon
+---
+
+## Benutzung der Klasse Icon
+
+Stud.IP stellt eine reichhaltige Auswahl an Icons zur Verfügung. Ab Stud.IP v3.4 werden diese Icons mit einer PHP-API angesprochen. In den früheren Versionen musste man dazu eine Pfad im Dateisystem kennen. Jetzt reicht es, die Form des Icons zu kennen, um dieses einzubinden.
+
+Zu diesem Zweck wurde die Klasse `Icon` geschaffen. Instanzen dieser Klasse haben 3 Eigenschaften:
+
+* die Form des Icons
+* die Rolle (und damit implizit die Farbe) des Icons
+* semantische Attribute wie `title`
+
+### Quick Start
+
+Wir wollen das "Startseite"-Icon (das man links oben auf jeder Stud.IP-Seite findet) einbinden. Dazu schreibt man einfach:
+
+```php
+<?= Icon::create('home', Icon::ROLE_NAVIGATION, ['title' => _("Zur Startseite")]) ?>`
+```
+
+Und damit erhält man ein `img`-Element wie dieses:
+
+https://develop.studip.de/studip/assets/images/icons/lightblue/home.svg
+
+Die Form des Icons ist "home". Die Rolle/Funktion des Icons ist "navigation". Das Icon soll ein semantisches Attribut, einen Titel, bekommen: _("Zur Startseite").
+
+Die Signatur von `Icon::create` sieht daher so aus:
+
+```php
+public static function create($shape, $role = Icon::DEFAULT_ROLE, $attributes = array())
+```
+
+Wenn man früher Icons verwenden wollte (`Assets::img("icons/16/lightblue/home.png")`), war dort die Farbe hartkodiert.
+Wollte man in seiner Installation ein Redesign vornehmen, ist das mehr als unschön.
+Aus diesem Grund wurden die Farben aus dem PHP-Code verbannt und auf Rollen umgestellt.
+
+Derzeit befindet sich die Abbildung der Rollen auf Farben in der Variablen `Icon::$roles_to_colors`.
+
+Will man eigene Icons verwenden, schreibt man einfach:
+
+```php
+Icon::create($eigeneURL)
+```
+
+### Icon-API im Detail
+
+Die Klasse `Icon` bietet nur wenige Methoden an.
+
+#### Factory-Methoden
+
+```php
+Icon::create($shape, $role = Icon::DEFAULT_ROLE, $attributes = [])
+```
+
+Das ist *die* Methode, um ein Icon zu instanziieren. Die `$shape` gibt die Form des Icons an, ohne weiter auf eine Färbung etc. einzugehen.
+
+Die Rolle `$role` definiert den Kontext, in dem das Icon verwendet werden soll. Der Stud.IP-Style-Guide gibt zB vor, dass alle Icons in Links einheitlich gefärbt sein sollen (in der Regel blau). Die Rolle hilft dabei, Farbwerte nicht hart zu kodieren und trotzdem im ganzen System einheitlich zu differenzieren.
+
+Die Eigenschaften `$attributes` enthalten **nur** semantische Attribute wie `title`. Nicht-semantische Werte wie CSS-Klassen, Größen, oder `data-`Attribute dürfen hier nicht eingetragen werden.
+
+#### Ausgabe-Methoden
+
+```php
+$icon->asImg($size = null, $view_attributes = [])
+```
+
+Diese Methode gibt das Icon als `img`-Element aus:
+
+```php
+Icon::create('vote', Icon::ROLE_CLICKABLE)->asImg(16)
+```
+
+erzeugt:
+
+```html
+<img width="16" height="16" src="images/icons/blue/vote.svg" alt="vote" class="icon-role-clickable icon-shape-vote">
+```
+
+Der erste Parameter `$size` legt die `width/height` des `img`-Elements fest. Die `$view_attributes` können mit beliebigen Attributen wie `class` usw. befüllt werden.
+
+```php
+$icon->asInput($size = null, $view_attributes = [])
+```
+
+Eine Variation von `Icon::asImg`, die das Icon als `input`-Element ausgibt:
+
+```php
+Icon::create('upload', Icon::ROLE_CLICKABLE)->asInput(20, ['class' => 'text-bottom'])
+```
+
+ergibt:
+
+```html
+<input type="image" class="text-bottom icon-role-clickable icon-shape-upload" width="20" height="20" src="images/icons/blue/upload.svg" alt="upload">
+```
+
+Die Parameter funktionieren wie bei `Icon::asImg`.
+
+```php
+$icon->asCSS($size = null)
+```
+
+Diese (selten verwendete) Methode gibt das Icon als CSS-Style-Angabe via @background-image@@ aus:
+
+```php
+Icon::create('vote+add')->asCSS(17)
+```
+
+generiert:
+
+```css
+background-image:url(images/icons/17/blue/add/vote.png);background-image:none,url(images/icons/blue/add/vote.svg);background-size:17px 17px;
+```
+
+Der Parameter `$size` legt die `background-size` fest.
+
+```php
+$icon->asImagePath()
+```
+
+Mit dieser Methode erhält man einfach den Pfad zum SVG, dass für das gewünschte Icon steht:
+
+```php
+Icon::create('vote+add')->asImagePath() === 'images/icons/blue/add/vote.svg'
+```
+
+```php
+$icon->__toString()
+```
+
+Die magische `__toString`-Methode ist nur ein Alias mit Default-Werten für `Icon::asImg`, sodass folgendes:
+
+```php
+echo Icon::create('vote+add')
+```
+
+einfach nur:
+
+```html
+<img width="16" height="16" src="images/icons/blue/vote.svg" alt="vote" class="icon-role-clickable icon-shape-vote">
+```
+
+ausgibt.
+
+#### Getter
+
+* `$icon->getShape()`
+* `$icon->getRole()`
+* `$icon->getAttributes()`
+
+Diese Methoden geben einfach die entsprechenden Werte zurück.
+
+#### "Setter"
+
+* `$anotherIcon = $icon->copyWithShape($shape)`
+* `$anotherIcon = $icon->copyWithRole($role)`
+* `$anotherIcon = $icon->copyWithAttributes(array $attributes)`
+
+Diese Methoden ändern nicht das `$icon`, sondern geben ein neues Icon mit verändertem `Shape/Role/Attributes` zurück. Instanzen von `Icon` sind `immutable`, so dass unerwünschte Seiteneffekt nicht auftauchen können.
+
+## Was fehlt noch?
+
+* `$size === false`
+* Additions
+* CSS-Mixins
+* Rollen-zu-Farben-Abbildung
diff --git a/docs/docs/functions/jsupdater.md b/docs/docs/functions/jsupdater.md
new file mode 100644
index 0000000..a05fdce
--- /dev/null
+++ b/docs/docs/functions/jsupdater.md
@@ -0,0 +1,53 @@
+---
+title: Periodische AJAX Updates
+---
+
+Man stelle sich vor, ein Plugin möchte, ohne die Stud.IP-Seite neu laden zu lassen, Informationen darüber abrufen, welches Buddys gerade online sind (so das OnlineBadge-Plugin aus Osnabrück). Ein Messenger möchte alle paar Sekunden abrufen, ob der Nutzer neue Nachrichten bekommen hat. Und die Seite selbst will abrufen, ob es im Forum einen neuen Beitrag gibt. Für Stud.IP war das zuvor nur umständlich möglich. Bei gängigen sozialen Netzwerken ist das hingegen Gang und Gebe.
+
+Ab der Version Stud.IP 2.2 gibt es einen zentralen Mechanismus, damit in dem obigen Fall nicht drei AJAX-Requests pro Sekunde gestartet werden. Das würde selbst leistungsfähige Server an den Rand der Verzweiflung führen. Wenn die Plugins die Klasse UpdateInformation benutzen, um die Daten zu bekommen, entlastet das den Server und den Programmierer, da er sich um einige Dinge nicht mehr kümmern muss.
+
+# Usecase 1: Plugins
+
+Ein Plugin, das zum Beispiel Nutzer anzeigen soll, die online sind, bedient sich der Klasse UpdateInformation, die in der Datei lib/classes/UpdateInformation.class.php liegt. Das Plugin sollte ein Systemplugin sein, damit es (also der Konstruktor) aufgerufen wird, bevor der Trails-Controller app/controllers/jsupdater.php zum Zuge kommt und die Daten wiederum abholt.
+
+Im PHP-Quellcode des Konstruktors des Systemplugins steht dann der Aufruf in etwa so:
+
+```php
+UpdateInformation::setInformation('myplugin', $data)
+```
+
+Die PHP-Variable $data (ein String, eine Zahl oder ein Array) wird dann an die Implementation des Updaters auf der JavaScript-Seite übergeben.
+
+Auf der JavaScript-Seite muss sich das Plugin registrieren, um diese Benachrichtigungen zu erhalten:
+
+```javascript
+STUDIP.JSUpdater.register("myplugin", receiveCallback, sendCallbackOrData)
+```
+
+Daraufhin passiert folgender Weg:
+
+* Die Javascript-Funktion STUDIP.JSUpdater.call wird alle paar Sekunden (mal häufiger, mal schneller, je nach Servergeschwindigkeit, Datenlage und Benutzeraktivität) aufgerufen und ruft selbst per Ajax-Request die Seite dispatch.php/jsupdater/get auf.
+* Hinter dispatch.php/jsupdater verbirgt sich der Trails-Controller in app/controllers/jsupdater.php und dessen Methode get_action().
+* Bevor der Controller irgendwas macht, werden die Konstruktoren der Systemplugins aktiviert. Das Plugin myplugin sammelt Daten und gibt sie an UpdateInformation::setInformation weiter. Die Daten sind jetzt registriert und bereit zur Weitergabe.
+* JsupdaterController ist jetzt dran und ruft UpdateInformation::getInformation() auf.
+* Bei UpdateInformation::getInformation() gibt die Daten, die es von den Plugins bekommen hat als Array zurück.
+* JsupdaterController nimmt dieses riesige Array als ein JSON-Objekt.
+* Dieses JSON-Objekt kommt bei der Javascript Funktion STUDIP.JSUpdater.processUpdate an, die die einzelnen Benachrichtigungen an die entsprechenden registrierten Handler weitergibt.
+
+# Usecase 2: Kernfunktionalität
+
+Auch wenn man es manchmal vergisst: Funktionalität in Stud.IP wird nicht nur durch Plugins bereit gestellt. Kernfunktionalität, die periodische AJAX-Updates verwenden möchte, benutzt nicht die Klasse UpdateInformation, sondern erweitert die Methode JsUpdaterController#coreInformation() einfach um ein paar Zeilen. Dort wird ein Array $data initialisiert und am Ende zurück gegeben, das assoziativ ist. Die Indexeinträge sind der Name der Javascript-Funktion (ohne "STUDIP." davor) und die Values sind dann beliebig.
+
+Auf PHP-Seite gibt es diese weiteren Methoden zum Zugriff auf übergebene Daten in Javascript:
+
+* `UpdateInformation::hasData($index)` - prüft, ob Daten unter dem angegebenen Index vorliegen
+* `UpdateInformation::getData($index)` - liefert die Daten unter dem angegeben Index zurück (bzw. `null`, falls keine Daten vorliegen.
+
+Auf JavaScript-Seite sieht die API des JS-Updaters so aus:
+
+* `STUDIP.JSUpdater.start()` - Startet den JS-Updater
+* `STUDIP.JSUpdater.stop()` - Beendet den JS-Updater
+* `STUDIP.JSUpdater.register(index, receiveCallback, sendCallbackOrData)` - Meldet ein neues Objekt unter dem angegeben Index beim Updater an. Zurückgelieferte Daten werden von dem angegebenen `receiveCallback` verarbeitet und in `sendCallbackOrData` angegebene Daten werden bei jedem Aufruf des Updaters mitgesendet. `sendCallbackOrData` kann dabei entweder ein reguläres JavaScript-Array, ein Objekt oder eine Funktion sein, die die Daten dynamisch zurückliefert.
+* `STUDIP.JSUpdater.unregister(index)` - Entfernt ein zuvor angemeldetes Objekt.
+
+Der JS-Updater wurde optimiert, so dass zum einen zu jedem Zeitpunkt nur noch ein einziger Aufruf des Updaters stattfindet und zum Anderen wird besser auf Lastsituationen am Server reagiert. Ebenso wird die Anzahl der Abfragen reduziert, wenn sich das Fenster mit Stud.IP im Hintergrund befindet und somit inaktiv ist.
diff --git a/docs/docs/functions/less.md b/docs/docs/functions/less.md
new file mode 100644
index 0000000..3c53dc6
--- /dev/null
+++ b/docs/docs/functions/less.md
@@ -0,0 +1,71 @@
+---
+id: less
+title: Stylesheets in LESS
+sidebar_label: Less
+---
+
+
+Ab Version 2.3 ([#2602](https://develop.studip.de/trac/ticket/2602)) von Stud.IP werden Stylesheets in LESSCss (http://lesscss.org) geschrieben und in normales CSS kompiliert.
+
+Das Kompilieren der .less-Dateien ist nun Teil des Buildprozesses bzw. kann auch separat mittels `make` auf der Kommandozeile angestossen werden.
+
+## Spezielle Variablen und Mixins für Stud.IP
+
+### Pfade
+
+| Variable | Beschreibung |
+| ---- | ---- |
+| `@image-path` | Pfad zu den Bildern von Stud.IP, entspricht im Standardfall `public/assets/images`|
+
+### Bilder
+
+| Variable | Beschreibung |
+| ---- | ---- |
+| `.retina-background-image(@normal, @retina)` | Bindet ein Hintergrundbild für Retina-Displays in zwei Größen ein. Die Bilder werden hierbei im Image-Pfad von Stud.IP erwartet. |
+Für Retina-Bilder stehen die beiden folgenden Funktionen zur Verfügung:
+
+
+### Icons
+
+Ab v3.4 können Stud.IP-Icons in LESS/CSS über folgenden Mixins eingebunden werden:
+
+| **Zweck** | **Signatur** | **Beispiel**|
+| ---- | ---- | ---- |
+|**Icon im Hintergrund** |`.background-icon(shape, role);` |`.background-icon('seminar', 'clickable');` |
+|**Button mit Icon inkl. Hover-Effekt** |`.button-with-icon(shape, role, role_hover)` |`.button-with-icon("accept", "clickable", "info_alt")` |
+|**Icon als BG in ::before** |`.icon("before", shape, role)` |`.icon("before", "arr_1right", "clickable")`|
+|**Icon als BG in ::after** |`.icon("after", shape, role)` |`.icon("after", "arr_1right", "clickable")` |
+
+### Farben
+
+// TODO: Tatsächliches Farbschema als Screenshot anzeigen
+// TODO: Namensräume besser beschreiben
+
+Seit Version 3.0 gibt es für Stud.IP ein eigenes Farbschema, auf welches - soweit möglich - immer zurückgegriffen werden sollte.
+
+| Farbe | Beschreibung |
+| ---- | ---- |
+| `@red` | Ein Rot-Ton |
+| `@orange` | Ein Orange-Ton |
+| `@activity-color` | Kennzeichnet Aktivitätsmöglichkeiten |
+| `@dark-gray-color` | Dunkles Grau |
+| `@light-gray-color` | Helles Grau |
+| `@content-color` | Farbe für Inhalte |
+| `@base-color` | Grundfarbe |
+
+
+Alle Farben gibt es jeweils in vier weiteren Abstufungen, bei denen jeweils 80%, 60%, 40% bzw. 20% der Originalfarbe gemischt wurden. Diese Farben spricht man indem, man der Farbvariable den jeweiligen Prozentsatz mit einem - getrennt anhängt, bspw. `red-60`.
+
+## Deprecated: LESS in Plugins
+
+Die folgenden Vorschläge werden sich in Zukunft (vermutlich Stud.IP v5) ändern und sind damit nicht zukunftssicher.
+
+Die Klasse `StudipPlugin` stellt die Methode `addStylesheet()` zur Verfügung, über welche LESS auch in Plugins verwendet werden kann. Dazu muss dieser Funktion der Name der LESS-Datei **relativ** zum Pluginpfad angegeben werden. Dadurch wird die LESS-Datei kompiliert und gleichfalls in der Seite zur Verfügung gestellt. Alle Mixins, die im Kern zur Verfügung stehen, stehen auch dem Plugin zur Verfügung.
+
+Darüber hinaus steht seit Stud.IP 3.4 den Plugins im LESS die Variable `@plugin-path` zur Verfügung, um auf Dateien innerhalb des Pluginverzeichnisses zu referenzieren.
+
+#### Deprecated: Eigene Implementierungen zum Speichern der kompilierten Dateien
+
+Falls die Speicherung der kompilierten Dateien geändert werden soll, kann der `Assets\Storage` über die Methode `setFactory()` eine Instanz einer eigenen Implementierung von `Assets\AssetFactory` übergeben, welche spezialisierte `Assets\Asset`-Objekte erzeugt, die die Speicherung anders verwalten können. Auch der Downloadpfad für den Zugriff auf die Dateien kann dabei entsprechend abgeändert werden. Für nähere Informationen wird auf die Interfaces [AssetFactory](https://develop.studip.de/trac/browser/trunk/lib/classes/assets/AssetFactory.php) und [Asset](https://develop.studip.de/trac/browser/trunk/lib/classes/assets/Asset.php) bzw. deren konkreten Kern-Implementierungen [PluginAssetFactory](https://develop.studip.de/trac/browser/trunk/lib/classes/assets/PluginAssetFactory.php) und [PluginAsset](https://develop.studip.de/trac/browser/trunk/lib/classes/assets/PluginAsset.php) verwiesen.
+
+Eine solche Änderung kann über ein SystemPlugin eingespielt werden, sofern dieses als erstes Plugin geladen wird (kleinste "Position" in der Pluginverwaltung des Systems).
diff --git a/docs/docs/functions/log.md b/docs/docs/functions/log.md
new file mode 100644
index 0000000..3966c94
--- /dev/null
+++ b/docs/docs/functions/log.md
@@ -0,0 +1,85 @@
+---
+id: log
+title: Log
+sidebar_label: Log
+---
+
+Häufig muss man während der Programmierung, u.U. aber auch nachträglich auf dem Produktiv/Staging System debugging durchführen. Dazu werden dann meist Ausgaben erzeugt. Das ist unübersichtlich und auf dem Produktivsystem auch für alle sichtbar, wenn man nicht aufpasst. Es gibt auch Bereiche, wo man nur sehr schwierig mit debugging Ausgaben arbeiten kann, z.B. Loginvorgang, SSO Varianten, Webservices etc. Für solche Fälle steht eine Log-Klasse zur Verfügung stehen, mit der man flexibel eines oder mehrere Logs erzeugen kann, und die Ausgabe des Logs nach belieben steuern kann.
+
+Die Klasse [Log](https://gitlab.studip.de/studip/studip/-/blob/main/lib/classes/Log.php) steht ab Version 2.4 zur Verfügung. Es wird automatisch ein standard log initialisiert, welches in die Datei studip.log im temporären Stud.IP Verzeichnis geschrieben wird. Im Modus `development` wird der Log Level auf DEBUG gesetzt, ansonsten auf ERROR.
+
+# Allgemeines
+
+Über die Klasse `Log` können verschiedene benannte Logger gesteuert werden. Der standard Logger hat keinen speziellen Namen. Die logger können im einfachsten Fall einfach der Name einer Datei sein, in die die Meldungen geschrieben werden. Alternativ kann ein callback übergeben werden, der die Meldungen entgegen nimmt. Die Klasse definiert folgende Logging Level:
+
+* `FATAL`
+* `ALERT`
+* `CRITICAL`
+* `ERROR`
+* `WARNING`
+* `NOTICE`
+* `INFO`
+* `DEBUG`
+
+Es stehen gleichnamige Methode zur Verfügung, um eine Meldung auf dem entsprechenden Level abzusetzen.
+
+Statischer Aufruf:
+
+```php
+Log::warning('Dies könnte problematisch sein');
+```
+
+Aufruf über Instanz Methode:
+
+```php
+Log::get()->warning('Dies könnte problematisch sein');
+```
+
+Groß- und Kleinschreibung spielen keine Rolle, es reicht auch eine eindeutige Abkürzung, z.B. warn() statt warning().
+
+## Andere Logger erzeugen
+
+### Dateibasiert
+
+```php
+//neuen benannten logger erzeugen
+Log::set('meinlogger', '/tmp/meinlog.log');
+
+//Aufruf
+Log::info_meinlogger('Dies nur zu meiner Information');
+oder
+Log::get('meinlogger')->info('Dies nur zu meiner Information');
+```
+
+### Mit callback
+
+```php
+//logging per mail
+$logtomail = function($l)
+{
+ return mail('noack@data-quest.de', 'Log ' . ' ['.$l['level_name'].'] ', $l['formatted']);
+};
+//benannten logger ändern
+Log::get('meinlogger')->setHandler($logtomail);
+```
+
+### kombinierte Logger mit verschiedenen Stufen
+
+```php
+$defaultlog = Log::get();
+$defaultlog->setLogLevel(Log::DEBUG);
+$maillog = Log::get('meinlogger');
+$maillog->setLogLevel(Log::ALERT);
+$combi_handler = function($m) use ($defaultlog, $maillog)
+{
+ $maillog->log($m['message'], $m['level']);
+ $defaultlog->log($m['message'], $m['level']);
+};
+//als neuen standard logger setzen
+Log::set(*, $combi_handler );
+
+//das geht nur in die Datei
+Log::debug('eigentlich egal was hier passiert');
+//in Datei und per mail
+Log::fatal('jetzt krachts aber richtig');
+```
diff --git a/docs/docs/functions/migrations.md b/docs/docs/functions/migrations.md
new file mode 100644
index 0000000..1557de3
--- /dev/null
+++ b/docs/docs/functions/migrations.md
@@ -0,0 +1,230 @@
+---
+id: migrations
+title: Migrationen
+sidebar_label: Migrationen
+---
+
+## Grundlegendes zu Migrationen
+
+Im Zusammenhang mit einem Update notwendige Änderungen (vor allem) am Datenbankschema oder Inhalten in der Datenbank werden in *Migrationen* verpackt - das sind kleine, isolierte Transformationen, die beim Update in einer definierten Reihenfolge ausgeführt werden können, um von einem Versionstand zu einem anderen zu gelangen. Falls die Migrationen das vorsehen, ist auf dem gleichen Weg ein Rollback bzw. Downgrade des Datenbankschemas möglich.
+
+Für die Verwendung in Plugins (oder für standortbezogene Schemaerweiterungen) gibt es noch Migrationsdomänen: Jede Domäne enthält eine komplett eigenständige Sammlung von Migrationen, die unabhängig von Migrationen aus anderen Domänen ausgeführt werden können. Die aktuelle Schemaversion wird für jede Domäne separat gespeichert, damit besitzt insbesondere jedes Plugin einen eigenen Raum von Versionsnummern für seine Migrationen.
+
+### Struktur und Numerierung
+
+Im einfachsten Fall bilden die Migrationen eine lineare Abfolge, dies war bis zur Version 4.3 auch die einzig mögliche Anordnung:
+
+```mermaid
+graph LR
+ subgraph default
+ 0((0)):::dashed-->1-->2-->3-->4-->5-->6:::current
+ end
+ classDef current stroke:#000080
+ classDef dashed stroke-dasharray:2
+```
+
+Genau eine Position ist als "aktuelle Schemaversion" (hier im blau) markiert. Die Stelle "0" darf dabei nicht als Nummer einer echten Migration vergeben werden: Sie steht nur für die Position "vor allen weiteren Migrationen" (z.B. wenn noch gar keine ausgeführt wurde).
+
+Ab Stud.IP 5.1 kann es allerdings auch Abzeige geben - das ist beispielsweise sinnvoll, wenn man nachträglich zwischen Stand 2 und 3 noch weitere Migrationen unterbringen möchte:
+
+```mermaid
+graph LR
+ subgraph default
+ 0((0)):::dashed-->1-->2-->3-->4-->5-->6:::current
+ 2-->2.0
+ subgraph two [2]
+ 2.0((2.0)):::dashed-->2.1-->2.2:::current-->2.3
+ end
+ end
+ classDef current stroke:#000080
+ classDef dashed stroke-dasharray:2
+```
+
+Ein Abzweig ("Branch") kann an jeder beliebigen Stelle aufgehängt werden, eine Migration an dem entsprechenden Knoten muß dazu auch nicht existieren. Ein Abzweig trägt immer den Namen des Knotens, bei dem er abzweigt (im obigen Beispiel "2"), den Versionen auf dem Abzweig wird dieser Name (gefolgt von einem ".") vorangestellt, also "2.1" usw. Auch hier ist die Stelle "2.0" reserviert und kann nicht als echte Migration verwendet werden.
+
+Jeder Abzweig hat eine eigene Positionsmarkierung für die auf diesem Branch bereits ausgeführten Migrationen. In einer (gedanklichen) linearisierten Reihenfolge aller Migrationen sind alle Migrationen auf einem Branch zwischen dem Abzweigpunkt und seinem Folgeknoten auf dem übergeordneten Branch angeordnet. In diesem Fall wäre das also: 1, 2, 2.1, 2.2, 2.3, 3, 4, 5, 6.
+
+Auch Abzeige können ihrerseits natürlich wieder Abzweige bekommen (so tief man möchte):
+
+```mermaid
+graph LR
+ subgraph default
+ 0((0)):::dashed-->1-->2-->3-->4-->5-->6:::current
+ 2-->2.0
+ 4-->4.0
+ subgraph two [2]
+ 2.0((2.0)):::dashed-->2.1-->2.2:::current-->2.3
+ 2.1-->2.1.0
+ subgraph two-one [2.1]
+ 2.1.0((2.1.0)):::current-dashed-->2.1.1
+ end
+ end
+ subgraph four [4]
+ 4.0((4.0)):::dashed-->4.1:::current
+ end
+ end
+ classDef current stroke:#000080
+ classDef current-dashed stroke:#000080,stroke-dasharray:2
+ classDef dashed stroke-dasharray:2
+```
+
+### Struktur und Numerierung im Stud.IP-Kern
+
+Die Struktur der Migrationen im Kern sieht derzeit (mit einem kleinen Vorgriff auf 5.2) so aus:
+
+```mermaid
+graph LR
+ subgraph default
+ 0((0)):::dashed-->1-->5
+ 1-->1.0
+ 5-->5.0
+ subgraph one [1]
+ 1.0((1.0)):::dashed-->1.1-->1.2-->1.3-->1.4-.->1.327
+ end
+ subgraph five [5]
+ 5.0((5.0)):::dashed-->5.1-->5.2
+ 5.1-->5.1.0
+ 5.2-->5.2.0
+ subgraph five-one [5.1]
+ 5.1.0((5.1.0)):::dashed-->5.1.1-->5.1.2-.->5.1.25
+ end
+ subgraph five-two [5.2]
+ 5.2.0((5.2.0)):::dashed-->5.2.1-->5.2.2
+ end
+ end
+ end
+ classDef dashed stroke-dasharray:2
+```
+
+Alle "alten" Migration bis einschließlich zur 5.0 liegen auf einem Branch "1", und für aktuelle Releases ≥ 5.1 gibt es jeweils einen eigenen Branch mit dem Names des Releases und den dazugehörigen Migrationen. Bei Service-Releases können diese Branches unabhängig voneinander mit zusätzlichen Migrationen bestückt werden.
+
+### Aufbau von Migrations-Dateien (d.h. Klassen):
+
+Grundsätzlich müssen alle Migrationsklassen die Klasse `Migration` erweitern.
+
+Sie bestehen in der Grundlage aus mindestens zwei Funktionen `up()` und `down()`. In `up()` werden die Änderungen für diesem Schritt durchgeführt und entsprechend in `down()` die Änderungen der `up()` Methode wieder rückgängig gemacht, soweit das sinnvoll ist. (*->siehe Irreversible Migrationen*)
+
+Die optionale Funktion `description()` liefert eine kurze Beschreibung der durchzuführenden Migration.
+
+In Kernmigrationen sollten keine APIs aus dem Kern verwendet werden (beispielsweise `Config`), da nicht gewährleistet ist, dass diese sich nicht ändern werden. Es sollten also z.B. immer die entsprechenden Datenbankeinträge manuell vorgenommen werden. Für Plugins gilt dies nicht: Pluginmigrationen sollten immer die entsprechende API verwenden. Falls Pluginmigrationen APIs des Plugins verwenden, kann es allerdings analoge Probleme zu Kern-APIs in Kernmigrationen geben - also hier auch vorsichtig sein.
+
+### Namensgebung für die Dateien
+
+Die Migrationsdateien sollen fortlaufend nummeriert werden, beginnend bei 1 und haben immer ganzzahlige Versionsnummern (1, 2, 3, usw.). Es sollten keine (unnötigen) Löcher in der Nummerierung vorhanden sein, führende Nullen dürfen verwendet werden (z.B. "001" statt "1") - diese haben keine Auswirkung auf die Einsortierung. Zusätzlich zur Version kann es einen Branch geben, der der Versionsnummer vorangestellt wird, z.B. "5.1" - die komplette Bezeichnung ist dann *Branch*`.`*Version*`_klassenname.php`. Beispiele dafür wären 3.1 oder 289.5.2. Der Branch ist optional und kann verwendet werden, um nachträglich Migrationen "zwischen" vorherige Migrationen schieben zu können.
+
+Als Analogie kann man sich die Kombination aus Branch und Versionsnummer wie eine Software-Versionsbezeichnung vorstellen. Daraus ergibt sich auch implizit eine Reihenfolge aller Migrationen.
+
+Für das Stud.IP-Release wird ab der Version 5.1 folgendes Numerierungsschema verwendet (siehe auch das Diagramm oben):
+
+* Alte Migrationen (vor 5.1) haben Nummern auf dem 1.x Branch, also "1.1" usw. Neue Migrationen auf diesem Branch kann es nicht mehr geben.
+* Migrationen ab 5.1 bekommen Nummern gemäß der Version, ab der sie hinzugefügt wurden - z.B. "5.1.1", "5.2.3" usw.
+* Migrationen mit Fehlerkorrekturen bekommen Nummern gemäß der ältesten Version, in der sie landen sollen - allerdings keinesfalls vor 5.1 (denn vorher gab es das neue Schema ja nicht).
+
+Jede Domäne (d.h. jedes Plugin) hat eine eigenständige Zählung der Migrationsschritte und Plugins müssen sich auch nicht an das o.g. Schema halten, d.h. sie können weiterhin einfach ihre Migrationen fortlauftend ab 1 numerieren. Plugin-Migrationen können aber natürlich auch in Branches aufgeteilt werden.
+
+### Rückportierung von Migrationen in alte Versionen
+
+Grundsätzlich ist die Idee, dass neue Migrationen, bei denen eine Rückportierung notwendig ist, direkt eine Versionsnummer auf dem frühesten Branch bekommen, in den sie portiert werden sollen (entspricht also der Versionsnummer am Ticket). D.h. die Migration hat auf allen Release-Branches die gleiche Bezeichnung.
+
+**Achtung**: Das funktioniert natürlich nicht für Stud.IP Versionen vor 5.1 - denn dort existieren ja gar keine in Branches aufgeteilte Migrationen. D.h. bei einer Rückportierung in eine Version vor 5.1 bekommt die Migration zwei unterschiedliche Nummern:
+- eine Nummer mit "5.1.x" für Stud.IP-Version 5.1 und größer
+- eine Nummer basierend auf dem Datum (altes Namensschema) für Stud.IP-Version 5.0 und darunter
+
+### Reversible und Irreversible Migrationen
+
+Bei reversiblen Migrationen besteht die Möglichkeit über die `up()` und `down()` Methoden immer in eine andere Version zu springen. Bei irreversiblem Migrationen verändert die `up()` Funktion die vorhandenen Daten derart, dass ein Aufruf der `down()` Funktion diese nicht wieder herstellen kann. In solchen Fällen sollte eine Fehlerbehandlung in der `down()` Funktion des Migrationsschrittes erfolgen.
+
+### Ausführung von Migrationen
+
+Für die Ausführung von Migrationen gibt es zwei Möglichkeiten:
+
+#### Ausführung über die Kommandozeile
+
+In dem Ordner `cli` befindet sich ein Skript, das die Migrationen über die Kommandozeile ausführt. Hier ist auch das Anstoßen von Migrationen in Plugins möglich, die vom Webinterface nicht direkt unterstützt werden.
+
+##### Mögliche Parameter
+
+| Parameter | Beschreibung |
+| ------ | ------ |
+| d | Domäne (default studip) |
+| m | Dateipfad zum Ordner mit den Migrationsdateien |
+| l | Nur auflisten was getan werden soll nicht migrieren |
+| t | Zielmigration (0 für komplettes Reset, andernfalls Zielversionsnummer) |
+| b | Branch, auf dem die Migration passieren soll (optional) |
+| v | verbose (empfohlen) |
+
+Beispiel: Stud.IP Migrationen von 6 rückgängig machen auf 5:
+`cli/migrate.php -d studip -t 5 -v`
+
+Beispiel: Ausgabe mit l Parameter:
+`cli/migrate.php -d studip -l -t 18`
+
+```
+ 3 Step87ExternConfigurations Extends table extern_config and converts configurations for the external pages from
+ INI-style files to serialised arrays stored in the database.
+ 4 Step116ParticipantView creates table necessary for StEP116
+ 5 Step25RaumzeitMigrations modify db schema for StEP00025; see logfile in $TMP_PATH
+ 6 Step25RaumzeitDbConversion convert dates for StEP00025; see logfile in $TMP_PATH
+ 7 TableTokenClass creates table for Token class
+ 8 Step117StudienModule modify db schema StEP00117 Studienmodulstrukturen;
+ 9 StEP00111Admission creates table admission groups
+ 10 ImageProxy creates table image_proxy_cache and config entry EXTERNAL_IMAGE_EMBEDDING
+ 11 LockRules creates table for lock rules
+ 12 Step120Userpic modify existing user pictures according to Step00120
+ 13 RemoveFieldsFromExtermine removes expire|repeat|color|priority from table ex_termine
+ 14 StEP00123Admission2 modifies table seminare, adds field `admission_enable_quota`
+ 15 Step00129EmailRestriction Adds the new Value EMAIL_DOMAIN_RESTRICTION to table config.
+ 16 Step00126EmbeddingFlashMovies Adds the new values EXTERNAL_
+ FLASH_MOVIE_EMBEDDING and DOCUMENTS_EMBEDD_FLASH_MOVIES to table config.
+ 17 DbOptimierungKontingentierung adds keys in admission_seminar_studiengang, admission_seminar_user and seminar_user
+ 18 Step00139UploadFileReorg reorganize uploaded files into sub-folders@@
+````
+
+#### Ausführung über die Web-Oberfläche
+
+Das *web_migrate* Skript unter `/public/web_migrate.php` hat die gleichen Funktionen wie die oben beschriebene Kommandozeiolenversion, kann aber interaktiv verwendet werden (siehe Screenshot).
+
+![Bildschirmfoto_2021-11-12_um_11.04.40](../assets/13c6d38bf74b403424e42c16f8308bfe/Bildschirmfoto_2021-11-12_um_11.04.40.png)
+
+Wählt man links einen anderen Branch als "default" aus, werden nur Migrationen auf bzw. unterhalb des gewählten Branches zur Ausführung angeboten. Liegen Migrationen direkt auf dem gewählten Branch, können diese auch direkt ausgewählt werden (analog zur Option `-t` in der cli-Version) - die Verarbeitung endet dann, wenn die markierte Migration erreicht ist.
+
+## Beispielplugin mit Migration
+
+Elmar hat einfach mal ein kleines Beispiel fertiggemacht: das "DummyPlugin" (ein Plugin, da die Verwendung für die Plugin-Schnittstelle vorrangig von Interesse ist). Die Katalogstruktur des Plugins sieht folgendermaßen aus:
+```
+ DummyPlugin.class.php
+ plugin.manifest
+ sql/
+ sql/dummy_install.sql
+ sql/dummy_uninstall.sql
+ migrations/
+ migrations/1_test.php
+ migrations/2_foo.php
+```
+
+Die SQL-Dateien unter sql definieren wie gehabt das "Grundlayout" für das Plugin und sind entsprechend als "dbscheme" (bzw. "uninstalldbscheme") im Manifest eingetragen, soweit also nichts neues. Neu hingekommen ist der Katalog migrations, der die einzelnen Deltas enthält, die von Plugin-Version zu Plugin-Version nachgezogen werden müssen (diese werden also nicht mehr in sql/dummy_install.sql abgebildet). Jeder Versionsschritt des Plugins kann beliebig viele solche Deltas ( = Migrations) besitzen. Alle Migrations sind aufsteigend numeriert, die Dateinamen folgen dabei der Konvention `nummer_klassenname.php`.
+
+Jede Migration ist eine PHP-Klasse mit den Operationen up() und down(), die die jeweiligen Änderungen durchführen bzw. wieder zurücknehmen können. Als Beispiel hier der Inhalt von `migrations/1_test.php`:
+
+```php
+ <?
+ class Test extends Migration {
+ function up () {
+ $db = DBManager::get();
+ $db->exec("INSERT INTO dummy VALUES (42, 'axel')");
+ }
+
+ function down () {
+ $db = DBManager::get();
+ $db->exec("DELETE FROM dummy WHERE id = 42");
+ }
+ }
+ ?>
+```
+Anstatt Werte in eine Tabelle einzutragen könnte man natürlich ebenfalls neue Tabellen anlegen, Felder hinzufügen oder entfernen, Daten umwandeln oder in eine andere Tabelle kopieren, Dateien oder Kataloge anlegen, Rechte setzen usw. (es muß nichts mit der DB zu tun haben). Der Zugriff auf die Datenbank erfolgt dabei wie üblich über die [DBManager-Klasse](Howto/Datenbankzugriffe). Alles weitere passiert (bei Plugins) automatisch, d.h. beim Update eines Plugins werden - ausgehend vom aktuellen Versionsstand - alle notwendigen Änderungen (d.h. Migrations) durchgeführt bzw. bei einem Downgrade eines Plugins entsprechend wieder zurückgenommen.
+
+PS: Wenn man möchte, kann man natürlich auch "dbscheme" und "uninstalldbscheme" im Manifest weglassen und das Anlegen der kompletten DB-Struktur für das Plugin über Migrations erledigen.
+
+Für ein Beispiel für die Migration eines Plugins gibt es hier ein kleines Zip file:
+
+[dummy_plugin-v0.3.zip](../assets/0ca19cdf7ae62c47c4a74b0110030059/dummy_plugin-v0.3.zip)
diff --git a/docs/docs/functions/modaler-dialog.md b/docs/docs/functions/modaler-dialog.md
new file mode 100644
index 0000000..989db59
--- /dev/null
+++ b/docs/docs/functions/modaler-dialog.md
@@ -0,0 +1,179 @@
+---
+id: modaler-dialog
+title: Modaler Dialog
+sidebar_label: Modaler Dialog
+---
+
+### Serverseitige erzeugte Abfragen
+
+Um einen modalen Dialog zu erzeugen, kann man ganz einfach die Methode `PageLayout::postQuestion();` verwenden. Diese Methode kapselt in sich die Erzeugung eines entsprechenden `QuestionBox`-Objekts und setzt die entsprechenden Parameter. Die Abfrage wird dann analog zu den [`MessageBoxen`](MessageBox) bei nächstmöglicher Gelegenheit im System angezeigt.
+
+Die `QuestionBox` kann die Antwort sowohl als `GET` als auch als `POST`-Request absetzen. Im Standardfall bei `PageLayout::postQuestion()` wird ein `POST`-Request abgesetzt, wodurch eine einfache Unterscheidung zwischen Bestätigung und Ablehnung der Frage schon alleine durch die genutzte Request-Methode erreicht werden kann.
+
+Die Funktion bzw. die Erzeugung einer `QuestionBox` benötigt mindestens 1, maximal 3 Parameter.
+
+#### Parameter
+* `$question`: Der Frage bzw. die Aktion, die bestätigt werden soll
+* `[$approveParams]`: optional, Link Parameter für den URLHelper im Falle einer positiven Antwort, in der Form `['name' => wert, 'name2' => wert2]`.
+* `[$disapproveParams]`: optional, Link Parameter für den URLHelper im Falle einer negativen Antwort.
+
+Die Rückgabe der Methode ist ein `QuestionBox`-Objekt, welches noch weiter manipuliert werden kann.
+
+#### Weitere Methoden der QuestionBox
+
+Das `QuestionBox`-Objekt stellt darüber hinaus noch weitere Methoden zur Verfügung:
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+| `setApproveParameters(array $parameters)` | Setzt die Link Parameter für die positive Bestätigung|
+| `setApproveURL($url)` | Setzt die URL, die bei einer positiven Bestätigung aufgerufen werden soll|
+| `setDisapproveParameters(array $parameters)` | Setzt die Link Parameter für die negative Bestätigung |
+| `setDisapproveURL($url)` | Setzt die URL, die bei einer negativen Bestätigung aufgerufen werden soll |
+| `setBaseURL($url)` | Setzt die URLs für die positive und die negative Bestätigung auf den gleichen Wert |
+| `setMethod($method)` | Setzt die zu nutzende Request-Methode (es ist anzuraten, immer `POST` zu nutzen; in dem Fall wird auch immer ein gültiges [CSRF-Token](CSRFProtection) in dem Request enthalten sein) |
+| `includeTicket()` | Weist die QuestionBox an, ein frisches Stud.IP-Ticket beim Rendern einzufügen |
+
+
+#### Beispiel
+```php
+PageLayout::postQuestion(_('Wollen Sie dies wirklich löschen?'), $accept_url = *, $decline_url = *);
+```
+
+#### Screenshot
+![image](../assets/fbce782c9fa1a8778926c5f6ade1d5d4/image.png)
+
+
+### Clientseitige Dialoge (*data-dialog"*)
+
+Die Ziele dahinter sind sowohl ein einheitliches Verhalten von Dialogen innerhalb von Stud.IP als auch eine Erleichterung für den Entwickler. Im Idealfall muss kein JavaScript mehr angefasst werden, um Dialoge zu nutzen. Die einzige Anpassung auf Serverseite ist das Auszeichnen des HTML mit entsprechenden Attributen und das Entfernen des umgebenden Layouts, so dass nur der wirklich relevante Inhalt zurückgegeben wird.
+
+#### Einbindung
+
+Dialoge können im HTML an den Tags `<a>`, `<button>` und `<form>` über das Attribut **`data-dialog`** gesteuert werden. Derart ausgezeichnete Elemente werden bei aktiviertem Javascript ihre Inhalte in einem modalen Dialog anzeigen. Die Inhalte werden dabei per AJAX nachgeladen und mittels jQuery UI's Dialog-Widget angezeigt. Auf Serverseite kann ein Aufruf, der aus einem solchen Dialog erfolgte, an dem HTTP-Header `X-Dialog` erkannt werden.
+
+Sollte bereits ein Dialog geöffnet sein und ein entsprechend ausgezeichnetes Element innerhalb des Dialogs aufgerufen werden, so wird der aktuelle Dialog aktualisiert, man verbleibt also im Dialog.
+
+Zu beachten: In der Rückgabe enthaltene `<script>`-Tags werden gefiltert und ausgeführt. Dies ist kein Standardverhalten von jQuery UI's Dialog und sollte beachtet werden (auch wenn derartiges Inline-Javascript im Idealfall vermieden und stattdessen auf globale Handler zurückgegriffen werden sollte).
+
+#### Parameter
+
+Der Dialog kann über verschiedene Attributangaben oder HTTP-Header gesteuert werden, welche im Folgenden erläutert werden. Dabei gilt, dass die Attributangaben auch beliebig miteinander kombiniert werden können, bspw. `data-dialog="title=foo;size=auto;buttons=false"`.
+
+##### Titel
+
+Der Titel eines Dialogs ist standardmässig der Inhalt des title-Attributs des zugrundeliegenden Elements (sofern vorhanden) und fällt bei den Tags `<a>` und `<button>` auf den Textinhalt des Elements zurück. Der Titel kann über verschiedene Parameter gesteuert werden:
+
+| Parameter | Beschreibung |
+| ---- | ---- |
+|`data-dialog="title='Test Titel'"` |Setzt den Titel auf **Test Titel** |
+|HTTP-Header `X-Title: Test Titel 2` |Setzt den Titel auf **Test Titel 2** |
+
+##### Größe
+
+Die Größe des Dialogs ist standardmässig 2/3 der Breite und Höhe und des Browserfensters. Dieser Wert kann über optionale Parameter gesteuert werden, wobei die minimale Größe des Dialogs auf 320x200 Pixel festgesetzt wurde:
+
+| Parameter | Beschreibung |
+| ---- | ---- |
+|`data-dialog="width=X"` |Setzt die Breite des Dialogs auf **X** Pixel |
+|`data-dialog="height=Y"` |Setzt die Höhe des Dialogs auf **Y** Pixel |
+|`data-dialog="size=XxY"` |Fasst die Angabe der Breite von **X** Pixeln und Höhe von **Y** Pixeln zusammen |
+|`data-dialog="size=X"` |Erzeugt einen quadratischen Dialog mit **X** Pixeln Breite und Höhe |
+|`data-dialog="size=auto"` |Versucht, die Größe des Dialogs an den geladenen Inhalt anzupassen. |
+|`data-dialog="size=big"` |Erzeugt einen großen Dialog mit viel Platz. |
+|`data-dialog="size=medium"` |Erzeugt einen Dialog mit moderat viel Platz. |
+|`data-dialog="size=medium-43"` |Erzeugt einen Dialog im 4:3 Verhältnis von Lange zu Breite. |
+|`data-dialog="size=big"` |Erzeugt einen kleinen Dialog der wenig Platz einnimmt. |
+
+##### Buttons
+
+Standardmässig enthält jeder Dialog einen Button *Abbrechen* am unteren Rande des Dialogs, welcher den Dialog schliesst.
+
+Es wird auch versucht, Buttons aus der Rückgabe zu extrahieren, damit diese sich ebenfalls in der Button-Leiste des Dialogs einreihen können. Dabei werden nur die Tags `<a>` und `<button>` berücksichtigt, welche entweder direkt mit dem Attribut `data-dialog-button` ausgezeichnet sind oder sich unterhalb eines Elements befinden, welche mit dem Attribut `data-dialog-button` ausgezeichnet wurde.
+
+Sowohl Links als auch Formulare können auf diese Weise aufgerufen werden. Dies bedeutet im Besonderen dass, sich auch ein Speichern-Button eines Formulars am unteren Rande des Dialogs befinden kann.
+
+Zu beachten ist, dass ein vorhandener Link/Button mit dem Inhalt *Abbrechen* mit dem Standardbutton überschrieben wird, welcher den Dialog schliesst. Dieses Verhalten ist gewollt und sollte beim Entwickeln berücksichtigt werden.
+
+Die Button-Leiste kann über die folgenden Mechanismen komplett ausgeschaltet werden, was auch bedeutet, dass die Buttons nicht aus dem zurückgegebenen Inhalt extrahiert werden:
+
+| Parameter | Beschreibung |
+| ---- | ---- |
+| `data-dialog="buttons=false"` | HTTP-Header `X-No-Buttons` |
+
+
+##### Weitere Optionen
+
+Ein Dialog kann über die folgenden Mechanismen geschlossen werden:
+
+| Parameter | Beschreibung |
+| ---- | ---- |
+| `data-dialog="close"`| HTTP-Header `X-Dialog-Close |
+| HTTP-Header `X-Location: <url>` | Bei der Auswertung einer Rückgabe kann auch ein Verweis auf eine andere Seite angegeben werden, welche den Dialog verlässt. Dies geschieht über folgenden Mechanismus: |
+| `data-dialog="reload-on-close"` | Die den Dialog umgebende Seite kann beim Schliessen des Dialogs automatisch neu geladen werden |
+| `data-dialog="resize=false"` |Der Dialog kann starr eingestellt werden, er kann also in der Größe nicht vom Nutzer verändert werden |
+| HTTP-Header `X-WikiLink: <url>` | Über den HTTP-Header `X-WikiLink` kann eingestellt werden, zu welcher Seite das im Titel angezeigte Hilfe-Icon verweisen soll: |
+| `data-dialog="center-content"` | Der Inhalt des Dialogs kann sowohl horizontal als auch vertikal zentriert werden |
+| HTTP-Header `X-Dialog-Execute: <JS-Funktion, bspw. STUDIP.Foo.bar>` | Aus der Rückgabe heraus, kann eine beliebige JavaScript-Funktion aufgerufen werden, welcher der Body des Requests übergeben wird (falls dieser JSON ist, wird er entsprechend umgewandelt). Ist die übegebene JavaScript-Funktion ungültig (nicht definiert oder keine Funktion), so wird ein entsprechender Fehler geworfen. |
+| HTTP-Header `X-Dialog-Execute: {func: <JS-Funktion, bspw. STUDIP.Foo.bar>, payload: []}`| Alternativ kann in diesem Header ein JSON-kodiertes Array mit dem verpflichtendem Eintrag `func` als Funktionsnamen und dem optionalen Eintrag `payload` übergeben werden. Dies ist in Situationen notwendig, wo zwar der Dialog aktualisiert werden soll (als HTTML über den Body der AJAX-Response) aber auch über die angegebene Funktion `func` mittels des gelieferten Payloads Änderungen stattfinden sollen. |
+
+
+Über die **CSS-Klasse** `hide-in-dialog` können Inhalte gezielt in Dialogen versteckt werden.
+
+##### Unterstützte Events
+
+Beim Öffnen und Schliessen des Dialogs werden jeweils JavaScript-Events getriggert, um dem Entwickler die Möglichkeit zu geben, das Verhalten der Inhalte dynamisch zu erweitern/ändern.
+
+* Beim **Öffnen** wird der Event `dialog-open` getriggert
+* Beim **Öffnen** und beim **Ändern** des Inhalts des Dialogs via AJAX wird der Event `dialog-update` getriggert
+* Beim **Schliessen** wird der Event `dialog-close` getriggert
+
+Beiden Events wird der aktuelle Dialog sowie die Optionen beim Aufruf übergeben. Exemplarischer Beispielcode:
+
+```javascript
+(function ($) {
+ $(document).on('dialog-open', function (event, parameters) {
+ var dialog = parameters.dialog;
+ var options = parameters.options;
+
+ $(dialog).dialog('title', options.title + ' - adjusted');
+ });
+}(jQuery));
+```
+
+Beim Laden der Daten über AJAX wird nach dem Laden der Event `dialog-load` getriggert, welchem die Optionen und das verwendete jQuery-XMLHttp-Request (als `xhr`) übergeben wird.
+
+Je nachdem, wie der Dialog aufgerufen wurde, erfolgen die Events an unterschiedlichen Stellen:
+
+Wurde der Dialog implizit über das `data-dialog`-Attribut an einem Element geöffnet, werden die Events an eben diesem Element getriggert, während sie im expliziten/programmatischen Fall am globalen *document*-Objekt getriggert werden. Eine Ausnahme bildet der Event `dialog-update`. Dieser wird immer global am *document*-Objekt getriggert, damit er immer aufgerufen wird - unabhängig davon, ob das auslösende Element vorhanden ist oder nicht.
+
+### Clientseitige Dialoge zur Dateneingabe
+
+Dialoge, welche serverseitig zur Eingabe von Daten in Formularen geladen werden, sollen im Fehlerfall eine Fehlermeldung im Dialog anzeigen. Bei erfolgreicher Dateneingabe soll der Dialog geschlossen werden und die Seite, welche im Hintergrund des Dialoges sichtbar ist (und aus welcher der Dialog geladen wurde) neu geladen werden. Um dies zu ermöglichen, müssen folgende Dinge im Quellcode eingebaut werden:
+
+* Das Formular, welches im Dialog angezeigt wird, muss das data-dialog Attribut besitzen, welches den Wert "reload-on-close" besitzt.
+* Der Dialog wird über einen Link aufgerufen, bei dem ebenfalls das data-dialog Attribut mit dem Wert "reload-on-close" gesetzt ist.
+* In der Aktion im Controller wird im Fehlerfall eine Fehlermeldung via PageLayout::postError (oder PageLayout::postMessage(MessageBox::error())) ausgegeben.
+* Im Erfolgsfall wird in der Aktion im Controller der Header X-Dialog-Close hinzugefügt und nichts gerendert:
+
+```php
+<?php
+$this->response->add_header('X-Dialog-Close', '1');
+$this->render_nothing();
+```
+
+Damit wird das oben beschriebene Verhalten des Dialoges erreicht.
+
+### Clientseitige Abfragen
+
+Über die Methode `STUDIP.Dialog.confirm(question, yes_callback, no_callback);` kann eine Bestätigung einer Aktion abgefragt werden. Der Parameter `question` enthält den Text für die Bestätigung (wie bspw "Sind Sie sicher, dass Sie dieses Element löschen wollen?") und der `yes_callback` wird anschliessend aufgerufen, falls die Abfrage positiv bestätigt wurde. Der `no_callback` ist optional und würde in dem Fall aufgerufen werden, dass die Abfrage negativ bestätigt wird.
+Der Handler kann auch als [Deferred](http://api.jquery.com/category/deferred-object/) genutzt werden:
+
+```javascript
+STUDIP.Dialog.confirm('Sind Sie sicher?'.toLocaleString()).done(function () {
+ alert('Aktion wurde bestätigt');
+}).fail(function () {
+ alert('Aktion wurde nicht bestätigt');
+});
+```
+
+Als Frage kann auch ein boole'scher Wert übergeben werden, was dazu führt, dass die Abfrage sofort als bestätigt bzw. abgelehnt gehandhabt wird.
diff --git a/docs/docs/functions/multi-person-search.md b/docs/docs/functions/multi-person-search.md
new file mode 100644
index 0000000..2dd528a
--- /dev/null
+++ b/docs/docs/functions/multi-person-search.md
@@ -0,0 +1,52 @@
+---
+id: multi-person-search
+title: MultiPersonSearch-Klasse
+sidebar_label: MultiPersonSearch-Klasse
+---
+
+`lib/classes/MultiPersonSearch.class.php` stellt eine Klasse bereit, mit der ein Dialog zum Hinzufügen von mehreren Personen erstellt werden kann. Ist JavaScript aktiviert, wird dazu ein [Modaler Dialog](ModalerDialog#toc5) geöffnet, anderenfalls wird ein Fallback angezeigt.
+
+Attach:MultiPersonSearch.png
+
+## Einbau im View
+
+Mit dem folgenden Quelltext wird ein MultiPersonSearch Objekt erzeugt und als Link ausgegeben.
+
+```php
+$mp = MultiPersonSearch::get('eindeutige_id')
+ ->setLinkText(_('Beispiellink'))
+ ->setTitle(_('Titel des Dialogs'))
+ ->setDefaultSelectedUser($defaultSelectedUser)
+ ->setExecuteURL($this->url_for('controller'))
+ ->setSearchObject($searchObj)
+ ->addQuickfilter(_('Name des Quickfilters'), $userArray)
+ ->render();
+
+print $mp;
+```
+
+
+### Übersicht wichtiger Methoden
+
+* *setLinkText($text)* setzt den Name des Links, der den Dialog öffnet.
+* *setTitle($title)* setzt den Namen des Dialogtitels.
+* *setDescription($desc)* setzt die Beschreibung des Dialogs, die unter dem Titel angezeigt wird.
+* *setDefaultSelectedUser($userArray)* setzt alle User, die bereits hinzugefügt sind (z. B. alle TeilnehmerInnen, die bereits in einer Veranstaltung eingetragen sind). *$userArray* ist ein Array bestehend aus User-Ids.
+* *setDefaultSelectableUser($userArray)* setzt ein Menge von Personen, die standardmäßig auf der linken Seite des Dialogs angezeigt werden. *$userArray* ist ein Array bestehend aus User-Ids.
+* *setExecuteURL($action)* setzt den Link des Controllers, der die Auswahl verarbeitet.
+* *setSearchObject($searchType)* setzt ein *SearchType* Objekt (z. B: SQLSearch), dass zur Suche von Personen verwendet wird.
+* *addQuickfilter($title, $userArray)* fügt einen Quickfilter, bestehend aus einem Titel und einem Array von User-Ids, hinzu.
+* *setJSFunctionOnSubmit($function_name)* fügt eine JavaScript Funktion hinzu, die ausgeführt wird, sobald auf den Button zum Speichern geklickt wird.
+* *setLinkIconPath($path)* setz ein Link-Icon (Standard-Wert: icons/16/blue/add/community.png).
+
+## Verarbeitung
+Um die über den Dialog ausgewählten Personen zu speichern, muss mittels `setExecuteURL($action)` eine entsprechende URL (z. B. zu einem Controller) bereitgestellt werden.
+
+Im Controller kann nun mittels `load($name)` das `MultiPersonSearch` Objekt geladen werden. Die Funktion `getAddedUsers()` liefert ein Array mit allen neu ausgewählten User-Ids zurück.
+```php
+$mp = MultiPersonSearch::load('eindeutige_id');
+
+foreach ($mp->getAddedUsers() as $userId) {
+ do_something($userId);
+}
+```
diff --git a/docs/docs/functions/new-html-structure.md b/docs/docs/functions/new-html-structure.md
new file mode 100644
index 0000000..4b75407
--- /dev/null
+++ b/docs/docs/functions/new-html-structure.md
@@ -0,0 +1,79 @@
+---
+id: new-html-structure
+title: HTML-Struktur
+sidebar_label: HTML-Struktur
+---
+Über die Jahre ist die grundlegende HTML-Struktur einer Stud.IP-Seite quasi organisch gewachsen und ist an vielen Stellen nicht gut zur Unterstützung der Barrierefreiheit geeignet. Ab Stud.IP 5.3 bekommt Stud.IP eine neue Struktur seiner Seiten, die semantischer daran orientiert ist, wofür jeder Bereich gedacht ist.
+
+# Grundlegende Struktur einer Stud.IP-Seite
+````html
+<html>
+ <head>
+ </head>
+ <body>
+ <div id="skip_link_navigation">Skiplinks</div>
+ <header id="main-header">
+ <div id="top-bar" role="banner">
+ <div id="responsive-menu">Hamburgermenü</div>
+ <div id="site-title">Globaler Titel der Installation ("Stud.IP")</div>
+ <div id="header-links">Dynamische Links (z.B. zum Entwicklerchat), Schnellsuche, Notifications, Avatarmenü</div>
+ </div>
+ <nav id="navigation-level-1">Hauptnavigation</nav>
+ <div id="current-page-structure">
+ <nav id="navigation-level-2">Navigation der aktuellen Seite</nav>
+ <div id="page-title-container">Titel der aktuellen Seite</div>
+ </div>
+ </header>
+ <aside id="sidebar">
+ Sidebar mit Navigation (#navigation-level-3) und sonstigen Widgets
+ </aside>
+ <main id="content-wrapper">
+ Der eigentliche Seiteninhalt
+ </main>
+ <a id="scroll-to-top">
+ Kurzlink zum Springen an den Seitenanfang
+ </a>
+ <footer id="main-footer">
+ <div id="footer-info">
+ Informativer Inhalt ("Angemeldet als...", ggf. Debuginfos)
+ </div>
+ <nav id="footer-navigation">
+ Navigationspunkte im Footer (Impressum, Datenschutzerklärung etc.)
+ </nav>
+ </footer>
+ <body>
+</html>
+````
+
+# Layout
+Zur Darstellung der Seitenelemente wird ein Gridlayout verwendet, das aktuell aus 2 Spalten und 3 Zeilen besteht.
+- Der gesamte Header bildet Zeile 1, diese erstreckt sich über die ganze Seitenbreite.
+- Die Sidebar liegt in Spalte 1, Zeile 2.
+- Der Inhalt bildet Spalte 2, Zeile 2.
+- Der Footer erstreckt sich ebenfalls über die gesamte Seitenbreite und beansprucht Zeile 3.
+
+# Überführung der alten Struktur in die neue
+Der alte Seitenaufbau wurde wie folgt in die neue Struktur überführt:
+
+| Altes Element | Neues Element |
+|-------------------------|--------------------------|
+| #layout_wrapper | *gibt es nicht mehr* |
+| #barBottomContainer | #top-bar |
+| #barBottomLeft | #responsive-menu |
+| #barTopFont | #site-title |
+| #barBottomright | #header-links |
+| #barTopAvatar | #avatar-menu-container |
+| #notification_container | #notification-container |
+| #header_avatar_menu | #avatar-menu |
+| #flex-header | #navigation-level-1 |
+| #barTopMenu | #navigation-level1-items |
+| #barTopStudip | #top-logo |
+| #layout_page | #current-page-structure |
+| #layout_context_title | #context-title |
+| .secondary-navigation | #navigation-level-2 |
+| #page_title_container | #page-title-container |
+| #current_page_title | #page-title |
+| #layout_container | *gibt es nicht mehr* |
+| #layout-sidebar | #sidebar |
+| section.sidebar | *gibt es nicht mehr* |
+| #layout_footer | #main-footer |
diff --git a/docs/docs/functions/notifications.md b/docs/docs/functions/notifications.md
new file mode 100644
index 0000000..4c61155
--- /dev/null
+++ b/docs/docs/functions/notifications.md
@@ -0,0 +1,355 @@
+---
+id: notifications
+title: Notifications
+sidebar_label: Notifications
+---
+
+## Notifications - ein Eventsystem für Stud.IP
+
+Plugins können in Stud.IP schon eine Menge. Sie können für jede Veranstaltung als Reiter eingefügt werden, sie können sich in die Homepage eines Nutzers mogeln und dort eine eigene Rubrik darstellen, sie können die Navigation umstellen und auf diese Weise komplette Fuktionen von Stud.IP wie das Forum ersetzen. Aber manchmal braucht man auch kleine Plugins, die nicht ganze Seiten verändern, sondern nur etwas tun, wenn etwas ganz bestimmtes passiert.
+
+Zum Beispiel: Wenn ein Nutzer eine Veranstaltung abonniert und mindestens auf der Warteliste steht, soll eine Person in der Buchhaltung, die Stud.IP fremd ist, eine automatische E-Mail bekommen. So eine Funktionalität ist in Stud.IP nicht integriert bisher, und der Entwickler, der sich damit befasst, will möglichst wenig im Quellcode von Stud.IP verändern. Er muss nun nur die betreffende Codezeile finden und setzt dort ein Event mit einem beliebigen Namen. Das geschieht so:
+
+`NotificationCenter::postNotification("user_accepted_to_seminar", $username);`
+
+Das Event heißt nun "user_accepted_to_seminar" und wird an dieser Stelle immer aufgerufen. $username ist einfach eine Variable, die in dem Kontext verfügbar ist. In diesem Fall ist das natürlich der Nutzername und der ist einfach wichtig, damit in der später verschickten Email auch drin steht, welcher Nutzer sich angemeldet hat. Aber theoretisch könnte man diese Variable auch weg lassen.
+
+Jetzt muss noch das Plugin geschrieben werden. Das Plugin muss vorher auf der Seite initialisiert sein, damit es eine Funktion für dieses Event registrieren kann. Ich schlage da natürlich ein SystemPlugin vor, das im Konstruktor sich selbst registriert:
+
+```php
+class MyPlugin extends StudIPPlugin implements SystemPlugin {
+ public function __construct() {
+ NotificationCenter::addObserver($this, "send_mail_to_accepted_user", "user_accepted_to_seminar");
+ }
+
+ public function send_mail_to_accepted_user($username) {
+ /* hier das, was getan werden soll, wenn der Nutzer registriert ist */
+ }
+}
+```
+
+An dieser Stelle wird klar, dass die übergebene Funktion nicht einfach eine Funktion sein sollte, sondern eine Methode eines Objektes. Zuerst wird also das spezifische Objekt übergeben und als zweiter Parameter der Name der Methode. Erst der dritte Parameter ist der Name des Events. In diesem Fall ist das Plugin faul und registriert sich selbst für das Event durch $this. Aber es kann auch ein anderes Objekt registriert werden als ein Plugin.
+
+Ab Stud.IP 4.2 ist es auch möglich, [Closures](php.net/manual/class.closure.php) als Event-Handler über die Funktion `on()` zu registrieren. Die Syntax folgt dabei der jQuery-Notation:
+
+```php
+NoticationCenter::on('UserDidDelete', function ($event, $user) {
+ // ...
+});
+```
+
+In Stud.IP 4.2 und 4.3 war hier nur die Übergabe von Closures möglich. Ab Stud.IP 4.4 können nahezu alle Callable-Typen übergeben werden. Lediglich Strings können nicht vernünftig abgebildet werden und sind deshalb zu vermeiden.
+
+### Rückgabewerte von Notifications
+
+Leider sind Notofications nicht dazu gedacht, etwas zurückzugeben. Aber wenn man Plugins baut, die etwas tun, möchte man vielleicht eine visuelle Rückmeldung geben wie eine Fehler- oder Erfolgsmeldung.
+
+Man kann von einem Observer keine Arrays oder andere Datenobjekte zurück bekommen. Aber es steht dem Observer natürlich frei, für sich die print oder echo-Anweisung zu verwenden, um Text an den Nutzer zu schreiben. Dies ist die einzige derzeit mögliche Art der Rückmeldung vom Observer; der eventuelle Rückgabewert der übergebenen Methode wird zurzeit komplett ignoriert.
+
+Jetzt kann also der Observer seiner Erfolgsmeldung schreiben a'la
+
+`echo '<div class="messagebox messagebox_success">Hurra! Daten erfolgreich übernommen.</div>';`
+
+Beachten muss man allerdings, dass dieses Echo vielleicht zur falschen Zeit kommt, wenn das Skript, das die Notification postet, mit Templates arbeitet oder gar ein Trails-Skript ist. Das ist natürlich nicht schlecht, führt nur dazu, dass die Fehlermeldung unter Umständen noch vor dem einleitenden `<html>`, also ganz ganz oben auf der Seite steht. Um das zu umgehen, kann das ausführende Skript schreiben:
+
+```php
+ob_start();
+NotificationCenter::postNotification("user_accepted_to_seminar", $username);
+$message = ob_get_contents();
+ob_end_clean();
+```
+
+Dank der Gnädigkeit von PHP funktioniert das auch, wenn im Hintergrund noch ein anderer Outputbuffer (für das das "ob_" steht) aktiv ist. Auf diese Weise kann man also zumindest einen String von den Observern zum ausführenden Skript bringen.
+
+**WARNUNG:** Jeder Gedanke, über diesen String serialisierte Arrays oder PHP-Objekte durchzuschleusen, ist naheliegend aber schmutzig, weil es ja immer sein kann, dass noch ein zweiter oder dritter Observer etwas sendet. Und zwei oder drei serialisierte Objekte in einem String lassen sich zumindest nicht so einfach mit einem `unserialize()` herausfischen. Also lasst sowas lieber gleich bleiben.
+
+Tipp: Man kann über Plugins nicht nur Observer für Notifications definieren, sondern natürlich auch Notifications selbst posten. Damit kann man quasi Plugins für das eigene Plugin ermöglichen. Dadurch können ganz konkret zwei Plugins miteinander kommunizieren, wenn beide installiert sind. Und wenn nur eines installiert ist, passiert nichts. Das kann unter Umständen ziemlich nützlich sein.
+
+
+### Notifications selber erzeugen
+
+#### Wie benenne ich eine Notification?
+
+Dazu gibt es bisher keine gültige Richtlinie. Offenbar scheint sich aber abzuzeichnen, dass der Name in CamelCase geschrieben und eine Handlung beschreibt. Da das NotificationCenter an die Originalimplementation aus NextStep angelehnt ist, macht es vermutlich Sinn, ähnliche Konventionen wie dort zu verwenden. So beschreibt der Artikel [NSNotfication Not Working, But Looks Right? :: Find That Bug!](http://www.goodbyehelicopter.com/?p=259) ein BestPractice, wonach die Notifications:
+
+`JJColorChange`
+
+und:
+
+`JJColorChanged`
+
+nur schwer im Code zu unterscheiden sind, da sie sich lediglich um einen Buchstaben unterscheiden. Die Empfehlung lautet statt:
+
+* CamelCase
+* CamelCased
+
+lieber:
+
+* CamelCase
+* CamelDidCase
+* CamelWillCase
+* CamelByCase
+
+zu verwenden. Aus diesen Gründen wurden bei den Notifications für Dateien, Wikiseiten und Forumsbeiträgen die Namen entsprechend gewählt.
+
+
+
+### Liste der in Stud.IP eingebauten Notifications
+
+Hier folgt nun eine Liste der verfügbaren Notifications, sortiert nach Bereichen. Bei der Parameterliste ist zu beachten, dass bei der durch die Notification aufgerufene Funktion oder Methode der erste Parameter den Namen der Notification beinhaltet.
+
+#### Veranstaltungen
+
+##### UserDidEnterCourse
+
+**Zusätzliche Parameter für Observer-Methode:**
+* Veranstaltungs-ID
+* Nutzer-ID
+
+**Sendebedingung:** Ein Nutzer trägt sich in eine Veranstaltung ein.
+
+##### UserDidLeaveCourse
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ?
+
+**Sendebedingung:** ?
+
+##### CourseDidChangeSchedule
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ?
+
+**Sendebedingung:** ?
+
+##### CourseDidGetMember
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ?
+
+**Sendebedingung:** ?
+
+
+
+
+#### Dateien
+
+##### DocumentWillCreate
+
+**Zusätzliche Parameter für Observer-Methode:**
+* StudipDocument-Instanz
+
+**Sendebedingung**: Eine Datei wird hochgeladen und wurde noch nicht angelegt.
+
+##### DocumentDidCreate
+
+**Zusätzliche Parameter für Observer-Methode:**
+* StudipDocument-Instanz
+
+**Sendebedingung**: Eine Datei wurde hochgeladen und angelegt.
+
+##### DocumentWillUpdate
+
+**Zusätzliche Parameter für Observer-Methode:**
+* StudipDocument-Instanz
+
+**Sendebedingung**: Eine Datei wird aktualisiert.
+
+##### DocumentDidUpdate
+
+**Zusätzliche Parameter für Observer-Methode:**
+* StudipDocument-Instanz
+
+**Sendebedingung**: Eine Datei wurde aktualisiert.
+
+##### DocumentWillDelete
+
+**Zusätzliche Parameter für Observer-Methode:**
+* StudipDocument-Instanz
+
+**Sendebedingung**: Eine Datei wird gelöscht.
+
+##### DocumentDidDelete
+
+**Zusätzliche Parameter für Observer-Methode:**
+* StudipDocument-Instanz
+
+**Sendebedingung**: Eine Datei wurde gelöscht.
+
+
+#### Forum
+
+##### PostingWillCreate
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ID des Forenbeitrags
+
+**Sendebedingung**: Ein Forenbeitrag wurde verfasst, aber noch nicht gespeichert.
+
+##### PostingDidCreate
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ID des Forenbeitrags
+
+**Sendebedingung**: Ein Forenbeitrag wurde verfasst und gespeichert.
+
+##### PostingWillUpdate
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ID des Forenbeitrags
+
+**Sendebedingung**: Ein Forenbeitrag wurde geändert, die Änderung aber noch nicht gespeichert.
+
+##### PostingDidUpdate
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ID des Forenbeitrags
+
+**Sendebedingung**: Ein Forenbeitrag wurde geändert und die Änderung gespeichert.
+
+##### PostingWillDelete
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ID des Forenbeitrags
+
+**Sendebedingung**: Ein Forenbeitrag wird gelöscht, der Löschvorgang hat jedoch noch nicht begonnen.
+
+##### PostingDidDelete
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ID des Forenbeitrags
+
+**Sendebedingung**: Ein Forenbeitrag wurde gelöscht.
+
+#### Literaturverwaltung
+
+##### LitListDidInsert
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ?
+
+**Sendebedingung:** ?
+
+##### LitListDidUpdate
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ?
+
+**Sendebedingung:** ?
+
+##### LitListDidDelete
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ?
+
+**Sendebedingung:** ?
+
+##### LitListElementDidInsert
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ?
+
+**Sendebedingung:** ?
+
+##### LitListElementDidUpdate
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ?
+
+**Sendebedingung:** ?
+
+##### LitListElementDidDelete
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ?
+
+**Sendebedingung:** ?
+
+
+#### Blubber
+
+##### PostingHasSaved
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ?
+
+**Sendebedingung:** ?
+
+
+#### Nachrichten
+
+##### MessageDidSend
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ?
+
+**Sendebedingung:** ?
+
+
+#### Nutzermigration
+
+Beim Migrieren von einem Nutzeraccount in einen anderen wird vor der Aktion die Notification `UserWillMigrate` und nach der Aktion die Notification `UserDidMigrate` gesendet. Beide Notifications erhalten die Id des Accounts, {+aus+} dem migriert werden soll als `subject`, während die Id des Accounts, {+in+} den migriert werden soll, als `$userdata` übergeben wird.
+
+
+#### Wiki
+
+Wenn eine Wikiseite verfasst wurde, wird vor dem tatsächlichen Speichern die Notification `PostingWillCreate` und nach dem Speichern `PostingDidCreate` versendet. Das `subject` ist ein Array mit `range_id` und `keyword` der Wikiseite.
+
+Wenn eine Wikiseite verändert wurde, wird vor dem tatsächlichen Speichern die Notification `PostingWillUpdate` und nach dem Speichern `PostingDidUpdate` versendet. Das `subject` ist ein Array mit `range_id` und `keyword` der Wikiseite.
+
+Auch wenn eine Wikiseite gelöscht wird, gibt es Notifications: `PostingWillDelete` und `PostingDidDelete`. Auch dort wird der Vollständigkeit halber ein Array mit `range_id` und `keyword` der Wikiseite übergeben.
+
+
+#### Veranstaltungsübersicht
+
+Beim Klick auf den Link "Alles als gelesen markieren" auf der Veranstaltungsübersicht wird vor der Aktion die Notification `OverviewWillClear` und danach die Notification `OverviewDidClear` gesendet. Beide Notifications erhalten die Id des Nutzers als `subject`.
+
+
+#### Modulverwaltung
+
+**Notifications: `CourseRemovedFromModule` und `CourseAddedToModule`**
+```php
+NotificationCenter::postNotification(
+ 'CourseRemovedFromModule',
+ $studyarea,
+ ['module_id' => $sem_tree_id, 'course_id' => $seminar_id]
+);
+
+NotificationCenter::postNotification(
+ 'CourseAddedToModule',
+ $studyarea,
+ ['module_id' => $sem_tree_id, 'course_id' => $seminar_id]
+);
+```
+
+<TODO: elmar oder anoack>
+
+
+#### Sidebar
+
+##### SidebarWillRender
+
+**Zusätzliche Parameter für Observer-Methode:**
+* ?
+
+**Sendebedingung:** ?
+
+
+#### Systemkonfiguration
+
+**Notification: `ConfigValueChanged`**
+
+Dies Notification wird versendet, wenn sich ein in der Klasse `Config` enthaltener Parameter geändert hat. Als `subject` wird die Config-Instanz mitgegeben und als `userdata` der neue und alte Wert.
+
+<TODO: elmar oder anoack>
+
+```php
+NotificationCenter::postNotification('ConfigValueChanged',
+ $this,
+ [
+ 'field' => $field,
+ 'old_value' => $old_value,
+ 'new_value' => $value_entry->value
+ ]
+);
+```
diff --git a/docs/docs/functions/oauth2.md b/docs/docs/functions/oauth2.md
new file mode 100644
index 0000000..a88ce54
--- /dev/null
+++ b/docs/docs/functions/oauth2.md
@@ -0,0 +1,90 @@
+---
+title: OAuth2
+---
+
+# Einrichtung
+
+Damit Stud.IP als Authorization Server im Rahmen von OAuth2 auftreten
+kann, müssen zuvor notwendig Schlüsseldateien erzeugt werden.
+
+Um zu überprüfen, ob eine Stud.IP-Installation bereits mit
+Schlüsseldateien versorgt ist, kann diese URL von
+`root`-berechtigten Nutzenden aufgerufen werden:
+`https://<STUD.IP-URL>/dispatch.php/admin/oauth2`
+
+Dort wird überprüft, ob diese Dateien vorhanden sind:
+
+- `config/oauth2/private.key`
+- `config/oauth2/public.key`
+- `config/oauth2/encryption_key.php`
+
+Ist das nicht der Fall, können diese Dateien mit folgendem Aufruf im
+Verzeichnis der Stud.IP-Installation erzeugt werden.
+
+```shell
+cli/studip oauth2:keys
+```
+
+Prüfen Sie hinterher unter obiger URL, dass alles ordnungsgemäß
+eingerichtet wurde.
+
+# Hinzufügen neuer OAuth2-Clients
+
+Auf der OAuth2-Konfigurationsseite (`https://<STUD.IP-URL>/dispatch.php/admin/oauth2`) haben Sie die Möglichkeit,
+OAuth2-Clients einzurichten. Klicken Sie dazu in der Sidebar auf
+"OAuth2-Client hinzufügen". Füllen Sie nun das angezeigte Formular aus.
+
+Wenn Sie erfolgreich einen Client hinzugefügt haben, erhalten Sie die
+`client_id` und ggf. das `client_secret`. **Beachten Sie bitte, dass
+das `client_secret` nur einmalig hier angezeigt wird.**
+
+# Verwalten von OAuth2-Clients
+
+Sie können auf der OAuth2-Konfigurationsseite
+(`https://<STUD.IP-URL>/dispatch.php/admin/oauth2`) vorhandene Clients einsehen und
+löschen. Ein Ändern der Konfiguration ist nicht vorgesehen. Wenn Sie
+die Details eines OAuth2-Clients ändern wollen, löschen Sie die
+vorhandene Konfiguration und legen eine neue an.
+
+# Konfiguration von OAuth2-Clients
+
+Nachdem Sie einen OAuth2-Client in Ihrer Stud.IP-Installation
+eingerichtet haben, besitzen Sie nun die `client_id` und ggfs. das
+`client_secret`.
+
+Außerdem benötigen Sie nun noch die notwendigen URLs:
+
+- Authorization URL: `https://<STUD.IP-URL>/dispatch.php/api/oauth2/authorize`
+- Access Token URL: `https://<STUD.IP-URL>/dispatch.php/api/oauth2/token`
+
+# Was unterstützt der Stud.IP OAuth2 Authorization-Server?
+
+## Grant Types
+
+Es werden folgende Grant Types laut Spezifikation unterstützt:
+
+- Authorization Code Grant
+- Authorization Code Grant with PKCE
+- Refresh Token Grant
+
+
+## Scopes
+Aktuell ist nur ein `scope` vorgesehen: `api`. Dieser Scope erlaubt
+vollen Zugriff auf alle Funktionen, die durch OAuth2 abgesichert werden.
+
+## PKCE-Verfahren
+Wenn neue Clients angelegt werden, wird abgefragt, ob der Client in
+der Lage ist, kryptografische Geheimnisse zu bewahren. Darunter fallen
+ausdrücklich also alle Apps. In diesem Fall muss das PKCE-Verfahren
+verwendet werden: https://oauth.net/2/pkce/
+
+# Aufräumen
+
+Mit der Zeit sammeln sich naturgemäß widerrufene oder abgelaufene
+Token in der Datenbank. Daher sollten regelmäßig folgendes Kommando
+im Verzeichnis der Stud.IP-Installation aufgerufen werden, um diese
+Token zu entfernen:
+
+```shell
+cli/studip oauth2:purge
+```
diff --git a/docs/docs/functions/page-layout.md b/docs/docs/functions/page-layout.md
new file mode 100644
index 0000000..e7b2ab6
--- /dev/null
+++ b/docs/docs/functions/page-layout.md
@@ -0,0 +1,189 @@
+---
+id: page-layout
+title: PageLayout
+sidebar_label: PageLayout
+---
+Über die Klasse PageLayout steht eine API in Stud.IP zur Verfügung, die verschiedene Anpassungen an der HTML-Grundstruktur der Ausgabe ermöglicht. Diess umfaßt einfache Dinge wie das Setzen des Seitentitels, ermöglicht aber auch das Hinzufügen oder Entfernen von HTML-Elementen im `<head>`-Bereich der Seite, um beispielsweise eigene Style-Sheets oder JavaScipt-Dateien einbinden zu können.
+
+Die Anpassung der HTML-Grundstruktur passiert über die neue Klasse `PageLayout`. Dazu bietet die Klasse eine Reihe statischer Methoden, die die verschiedenen Möglichkeiten abdecken.
+
+### Seitentitel
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+| **setTitle($title)** | Setzt den aktuellen Seitentitel, sowohl für die Anzeige im Browserfester als auch in Stud.IP. |
+
+Beispiel:
+```php
+PageLayout::setTitle(_('Startseite'));
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+| **getTitle()** | Liefert den aktuellen Seitentitel zurück. |
+| **hasTitle()** | Fragt ab, ob für die aktuelle Seite ein Seitentitel gesetzt wurde. |
+
+
+### Hilfe
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+| **setHelpKeyword($help_keyword)** | Setzt das Hilfe-Thema für die angezeigte Seite. Dieses wird dann beim Aufruf der Hilfe-Funktion an den Hilfe-Server übermittelt. |
+
+
+Beispiel:
+```php
+PageLayout::setHelpKeyword('Basis.Startseite');
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+| **getHelpKeyword()** | Liefert das eingestellte Hilfe-Thema zurück. |
+
+
+#### Reiternavigation
+| Funktion | Beschreibung |
+| ---- | ---- |
+| **setTabNavigation($path)** | Setzt den Pfad im Navigationsbaum, an dem die Reiternavigation startet. Es werden dann die beiden Ebenen unterhalb des angegebenen Navigationspunkts als Reiter (1. und 2. Ebene) angezeigt. Die Voreinstellung ist das jeweils aktive Element der Hauptnavigation. Ein explizites Setzen ist nur für Navigationskontexte mit Reiteranzeige notwendig, die an anderer Stelle als der Hauptnavigation eingebunden sind (wie z.B. das Impressum). Man kann auch die Anzeige der Reiternavigation ganz ausschalten, wenn man `NULL` als *$path* übergibt. |
+
+Beispiel:
+```php
+PageLayout::setTabNavigation('/links/siteinfo');
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+| **getTabNavigation()** | Liefert die Reiternavigation zurück. |
+
+### Hinzufügen von Inhalten
+
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+| **addStyle($content)** | Fügt eine neues CSS Style Element in den Seitenkopf ein. |
+
+Beispiel:
+```php
+PageLayout::addStyle('#highlight { background-color: red; }');
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**addStylesheet($source, $attributes = [])** | Fügt einen Verweis auf ein Style-Sheet in den Seitenkopf ein. *$source* kann entweder eine komplette URL oder ein Dateiname sein, der relativ zum Assets-Verzeichnis aufgelöst wird. Optional können weitere Attribute für das LINK-Element übergeben werden. |
+
+Beispiel:
+```php
+PageLayout::addStylesheet('print.css', ['media' => 'print']);
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**addScript($source)** | Bindet eine weitere JavaScript-Datei in den Seitenkopf ein. *$source* kann entweder eine komplette URL oder ein Dateiname sein, der relativ zum Assets-Verzeichnis aufgelöst wird. |
+
+Beispiel:
+```php
+PageLayout::addScript($this->getPluginURL() . '/vote.js');
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**addHeadElement($name, $attributes = [], $content = NULL)** | Fügt eine beliebiges HTML-Element in den Seitenkopf ein. *$name*, *$attributes* und *$content* entsprechen den Namen, der Attributliste und dem Inhalt des erzeugten Elements. Ist *$content* `NULL`, so wird das Element nicht abgeschlossen (wie META oder LINK), andernfalls wird automatisch auch ein schließendes Tag hinter dem Inhalt ausgegeben (z.B. bei SCRIPT). |
+
+Beispiel:
+```php
+PageLayout::addHeadElement('link', [
+ 'rel' => 'alternate',
+ 'type' => 'application/rss+xml',
+ 'title' => 'RSS',
+ 'href' => $feed_url,
+]);
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**addBodyElements($html)** | Fügt ein beliebiges HTML-Fragment direkt zu Beginn des BODY in die Seitenausgabe ein. Das ist vor allem in Plugins verwendbar, die Inhalte auf beliebigen Stud.IP-Seiten ausgeben wollen. |
+
+### Entfernen von Inhalten
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**removeStylesheet($source, $attributes = [])** | Entfernt einen Verweis auf ein Style-Sheet wieder aus dem Seitenkopf. *$source* kann wie bei **addStylesheet** entweder eine komplette URL oder ein Dateiname sein, der relativ zum Assets-Verzeichnis aufgelöst wird. |
+
+Beispiel:
+```php
+PageLayout::removeStylesheet('style.css');
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**removeScript($source)** | Entfernt eine eingebundene JavaScript-Datei wieder aus dem Seitenkopf. *$source* kann wie bei **addScript** entweder eine komplette URL oder ein Dateiname sein, der relativ zum Assets-Verzeichnis aufgelöst wird. |
+|**removeHeadElement($name, $attributes = [])** | Entfernt alle Elemente mit dem angegebenen Namen und den Attributen wieder aus dem Seitenkopf. |
+
+Beispiel:
+```php
+PageLayout::removeHeadElement('link', ['rel' => 'stylesheet']); // remove all style sheets
+```
+
+### Darstellung von Meldungen
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**postMessage(MessageBox $message)** | Veranlaßt das System, das angegebene [`MessageBox`-Objekt](MessageBox) bei nächster Gelegenheit anzuzeigen, d.h. bei der nächsten Ausgabe eines Layouts. Die Meldung bleibt so lange gespeichert, bis sie angezeigt wurde, auch über (ggf. mehrere) Redirects hinweg. |
+
+Für jeden Typen der `MessageBox` gibt es auch eine eigene `post<type>`-Methoden am `PageLayout`-Objekt, wie beispielsweise `PageLayout::postSuccess()` oder `PageLayout::postError()`.
+
+Beispiel:
+```php
+PageLayout::postMessage(MessageBox::success('Eintrag gelöscht'));
+// Äquivalent:
+PageLayout::postSuccess('Eintrag gelöscht');
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**clearMessages()** | Löscht alle Meldungen, die zur Anzeige hinterlegt und noch nicht ausgegeben wurden. |
+
+### Bestätigen von Aktionen
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**postQuestion($question, $accept_url = "", $decline_url = "")** | Holt eine Bestätigung des Nutzers zu einer bestimmten Aktion ein. Wird die Ausführung der Aktion bestätigt, so wird ein POST-Request auf die angegebene `$accept_url` abgesetzt, im anderen Fall wird die `$decline_url` über GET aufgerufen. Weitere Details finden sich im ersten Abschnitt unter [Modaler Dialog](ModalerDialog#server). Der Mechanismus funktioniert analog wie `postMessage()`, so dass die Bestätigung bei der nächsten Gelegenheit dargestellt wird. |
+
+Beispiel:
+```php
+PageLayout::postQuestion(
+ 'Wollen Sie diese Aktion wirklich ausführen?',
+ URLHelper::getURL('dispatch.php/foo/confimed')
+);
+```
+
+### Anzeige des Seitenkopfs
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**disableHeader()** | Unterdrückt die Anzeige des Seitenkopfs mit dem Navigationsbereich, z.B. für eine Druckansicht (die sollte aber besser mit einem Print-Style-Sheet gelöst werden) oder ein Popup-Fenster. |
+
+### Setzen des Id-Attributs des Elements `<body>`
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**setBodyElementId($id)** | Setzt die Id des `<body>`-Elements, um über dieses beispielsweise in CSS oder Javascript gezielter Elemente ansprechen zu können.
+| **getBodyElementId()** | Liefert die gesetzte Id des `<body>`-Elements zurück. Wurde keine Id gesetzt, wird `false` zurückgeliefert. |
+
+### Ersetzen der Schnellsuche
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**addCustomQuicksearch($html)** | Ersetzt die Schnellsuche (oben rechts) durch beliebiges HTML.
+| **hasCustomQuicksearch()** | Fragt ab, ob die Schnellsuche ersetzt wurde.
+| **getCustomQuicksearch()** | Liefert den HTML-Code zurück, der die Schnellsuche ersetzen soll. Wurde kein HTML durch `addCustomQuicksearch()` gesetzt, liefert diese Methode `null` zurück. |
+
+## Beispiel
+
+Zum Abschluß noch ein kleines Beispiel aus einem Plugin, das (u.a.) eine eigene CSS-Datei mitbringt:
+
+```php
+PageLayout::setTitle('Neueste Aktivitäten');
+PageLayout::setHelpKeyword('Plugins.Activities');
+PageLayout::addStylesheet($this->getPluginURL() . '/css/activities.css');
+```
diff --git a/docs/docs/functions/pdf-exports.md b/docs/docs/functions/pdf-exports.md
new file mode 100644
index 0000000..706ad1a
--- /dev/null
+++ b/docs/docs/functions/pdf-exports.md
@@ -0,0 +1,71 @@
+---
+id: pdf-exports
+title: PDF-Exporte
+sidebar_title: PDF-Exporte
+---
+
+In `lib/classes/exportdocuments/ExportPDF.class.php` liegt eine Klasse, mit der es einfach ist, aus in Stud.IP gängigem Text, der eventuell auch mit Stud.IP-Formatierung versehen ist, in ein PDF zu exportieren. Im Klartext heißt das: man hat einen Text wie den Inhalt eines Wikis und will daraus eine PDF bekommen, in der die Tabellen und Grafiken korrekt dartgestellt werden. Die Klasse ExportPDF erbt ihre schöne Einfachheit von der Klasse TCPDF, die noch über einige andere schicke Dinge verfügt. Das bedeutet, dass $doc auch alle Methoden von TCPDF benutzen kann.
+
+Durch die direkte Ausgabe des PDF-Dokumentes wird jede andere Ausgabe natürlich ignoriert, die vorher oder nachher geschrieben worden ist. Sobald dispatch aufgerufen wird, wird nur noch das PDF übermittelt (mitsamt MIME-Type). Der Parameter von disptach ist überdies der Dateiname ohne das ".pdf", was automatisch angefügt wird.
+
+## Generierung
+
+### Aus PHP
+
+Der Code zum Erzeugen eines PDF-Dokumentes aus Stud.IP Code besteht aus folgenden vier Zeilen:
+
+```php
+<?php
+$doc = new ExportPDF();
+$doc->addPage();
+$doc->addContent('Hallo, %%wir%% benutzen :studip: -Formatierung.');
+$doc->dispatch("test_pdf");
+```
+
+Zuerst muss man ein Dokument-Objekt initialisieren. In unserem Fall ist das `$doc`. Danach fängt man eine neue Seite an mit `$doc->addPage()` und befüllt diese Seite mittels der Methode `addContent` mit Inhalten, welche Stud.IP-Formatierungsangaben beinhalten müssen. In der letzten Zeile wird dieses Dokument an den Aufrufer der Seite ausgegeben.
+
+### HTML-Template verwenden
+
+Hat man ein Template, welches HTML-Code generiert, so kann dieser HTML-Code direkt an ExportPDF übergeben werden, um daraus ein HTML-Dokument zu bauen. Der Code zum Rendern des HTML-Codes und dessen Umwandlung in ein PDF-Dokument kann folgendermaßen aussehen:
+
+```php
+<?php
+$templateFactory = new Flexi_TemplateFactory(__DIR__ . '/../templates/');
+$template = $templateFactory->open('pdf_doc.php');
+$template->set_attribute('attr1', $attr1);
+$template->set_attribute('plugin', $this->plugin);
+
+$htmlCode = $template->render(); //$htmlCode beinhaltet den gerenderten HTML-Code
+
+//Konvertierung in ein PDF-Dokument:
+
+$pdfdoc = new ExportPDF();
+$pdfdoc->addPage();
+$pdfdoc->writeHTML($htmlCode);
+
+//PDF senden:
+
+$pdfdoc->dispatch('pdf_doc');
+```
+
+Anstatt der Methode `addContent` wird `writeHTML` aufgerufen. Diesem wird zuvor gerenderter HTML-Code, welcher unter Zuhilfenahme eines Templates erzeugt wurde, übergeben.
+
+
+### In Dateibereich speichern
+
+Das via ExportPDF erzeugte PDF kann direkt im Dateibereich von Stud.IP abgespeichert werden. Der Code dazu ist fast identisch wie in obigen Fällen:
+
+```php
+<?php
+$doc = new ExportPDF();
+$doc->addPage();
+$doc->addContent('Hallo, %%wir%% benutzen :studip: -Formatierung.');
+$studip_dokument = $doc->save("test_pdf", $folder_id);
+```
+
+Nur die letzte Zeile wurde ausgewechselt. Die Methode `save` speichert das Dokument und liefert ein Objekt vom Typ StudipDocument zurück. Man kann mittels dieses Objekte mit `$studip_dokument->getId()` die md5-ID des Dokumentes bekommen. Gibt man zudem eine folder_id beim Aufruf von `save` an, so wird die Datei auch direkt in einen Dateibereich eingefügt und man muss sich um nichts mehr kümmern.
+
+
+## Weiterführende Themen
+
+* Dokumentation von TCPDF: [http://www.tcpdf.org/docs.php](http://www.tcpdf.org/docs.php).
diff --git a/docs/docs/functions/personal-notifications.md b/docs/docs/functions/personal-notifications.md
new file mode 100644
index 0000000..d6d86c0
--- /dev/null
+++ b/docs/docs/functions/personal-notifications.md
@@ -0,0 +1,31 @@
+---
+id: personal-notifications
+title: Persönliche Benachrichtigungen
+sidebar_label: Persönliche Benachrichtigungen
+---
+
+Ab der Version 2.4 ist es sehr einfach, den Nutzer in Echtzeit über neue Inhalte oder spannende Aktionen, die ihn betreffen, zu informieren. Gedacht ist das für den Fall, dass er eine persönliche Nachricht bekommt oder jemand auf einen von ihm eingestellten Inhalt antwortet. Auf jeden Fall soll stets ein direkter Bezug zum Nutzer vorhanden sein.
+In diesen Fällen möchte man vielleicht sofort sehen, dass sich was getan hat und nicht erst beim nächsten Seitenneuladen. Gerade wenn Stud.IP in einem von 20 Browsertabs offen ist, kann das schon mal dauern. Da möchte man sofort reagieren können. Für Chat-artige Kommunikation zum Beispiel wäre das Gift. Die persönlichen Benachrichtigungen sind daher DAS Element, um Echtzeitereignisse zu ermöglichen.
+
+Über das Feature, wie der Nutzer das sieht, und wie er/sie darauf reagieren und das Feature einstellen kann, steht mehr in der Anwenderdoku.
+
+Hier soll es hauptsächlich um die Frage gehen: ich bin ein Entwickler und will da eine Benachrichtigung an den Nutzer abgeben. Was muss ich tun?
+
+Eigentlich nur eine Zeile einfügen. Nehmen wir an, Nutzer A würde gerne über alle Änderungen einer bestimmten Wiki-Seite benachrichtigt werden wollen. Und ein anderer Nutzer B ändert diese Seite. So muss man beispielsweise folgende eine Zeile in den Code einfügen:
+
+```php
+PersonalNotifications::add(
+ $user_A_user_id, //id of user A or array of ´multiple user_ids
+ $url_of_wiki_page, //when user A clicks this URL he/she should jump directly to the changed wiki-page
+ "User B changed wiki-page xyz", //a small text that describes the notification
+ "wiki_page_1234", //an (optional) html-id of the content of the wiki page. If the user is looking at the content already, the notification will disappear automatically
+ Icon::create("wiki", "clickable") //an (optional) icon that is displayed next to the notification-text
+);
+```
+
+
+Man braucht also nur die user_id von Nutzer A, die URL der Wikiseite, einen ganz kurzen Beschreibungstext (bitte nicht zu lang werden lassen, das sprengt sonst das Layout) und optional noch eine HTML-ID und ein Bild.
+
+Wozu die HTML-ID? Gerade bei einem Chat zum Beispiel will man nicht am Ende jeden eingegangenen Satz vom Gegenüber nochmal als Notification wegklicken. Da entstand der Gedanke, wenn man etwas schon gesehen hat auf der Seite, dann soll sich die Benachrichtigung auch von alleine erledigen. Wenn also Nutzer A per Zufall eh auf die Seite geht (durch ein rotes Icon in der meine_seminare-Übersicht) und die geänderte Wikieseite so sieht, dann muss er/sie nicht nochmal zusätzlich die Benachrichtigung wegklicken. Stattdessen wird automatisch ein AJAX-Request abgefeuert, der mitteilt, dass der Nutzer den Inhalt schon gesehen hat.
+
+Um das alles muss der Programmierer nicht besonders kümmern. Es muss nur beim `add`-Aufruf entweder eine HTML-ID angegeben werden oder eben nicht. So einfach geht das.
diff --git a/docs/docs/functions/qr-codes.md b/docs/docs/functions/qr-codes.md
new file mode 100644
index 0000000..76b3aaa
--- /dev/null
+++ b/docs/docs/functions/qr-codes.md
@@ -0,0 +1,16 @@
+---
+title: QRCodes erzeugen
+---
+
+Bei den Fragebögen gibt es ein nettes, kleines Feature: Man kann auf ein QR-Code-Icon klicken und bekommt im Vollbildmodus einen QR-Code des Links zum Fragebogen angezeigt.
+
+Auch Plugins oder andere Stellen in Stud.IP können relativ einfach einen QR-Code auf diese Weise erzeugen. Hat man einen `<a>`-Link, so muss man ihn nur mit dem Tag `data-qr-code` anreichern.
+
+```html
+<a href="http://localhost/studip_trunk/dispatch.php/questionnaire/answer/c9b030df1bd556c8383dc56259d0f9c3?cid=c0fd14b93d003f35bed648d2056346aa"
+ data-cr-code="Bitte nehmen Sie schnell teil.">
+ Link zur Umfrage
+</a>
+```
+
+Der Text in dem data-qr-code Tag ist optional und bietet eine Beschreibung, die unter dem erzeugten QR-Code angezeigt wird.
diff --git a/docs/docs/functions/quick-search.md b/docs/docs/functions/quick-search.md
new file mode 100644
index 0000000..ac99b9e
--- /dev/null
+++ b/docs/docs/functions/quick-search.md
@@ -0,0 +1,101 @@
+---
+id: quick-search
+title: QuickSearch-Klasse
+sidebar_label: QuickSearch-Klasse
+---
+
+
+In `lib/classes/QuickSearch.class.php` wird eine GUI-Klasse bereit gestellt, mit der man ein einzeiliges Suchfeld inklusive AJAX-Dropdown Menü schnell und einfach an jede Stelle einbauen kann. Vorteile:
+
+* Wenig und übersichtlicher Quellcode nötig.
+* AJAX-Suche und Javascript-Dropdown Menü gibt es gratis.
+* Das Suchfeld ist auch ohne Javascript benutzbar und genügt damit den Bestimmungen zur Barrierefreiheit von Stud.IP.
+* Es kann nach praktisch allem gesucht werden - nicht nur in der Datenbank, sondern ganz beliebig.
+* Das Suchfeld ist konfigurierbar durch weitere Funktionen.
+* Bei zukünftigen Änderungen an diese Suchfelder durch die GUI-Kommission ist der Programmierer mit dieser Klasse auf der sicheren Seite, weil nur die Klasse verändert werden muss.
+
+Im HTML gibt es dann das Input-Feld, das man erwartet und ein weiteres unsichtbares Input-Feld für die ID. Meistens sucht man nach etwas wie Personen, die einen klar lesbaren Namen haben. Aber der Programmierer will an der Stelle eigentlich nicht den Namen, sondern lieber eine user_id haben. Die user_id wird dann versteckt im Hintergrund gespeichert. Der Programmierer kann QuickSearch einbinden, wie ein Input-Feld, in das der Nutzer wie magisch nur die user_id eingegeben hätte - der Nutzer gibt aber den Klarnamen ein. QuickSearch bietet diese Architektur ganz automatisch.
+
+## Einbau eines QuickSearch Feldes
+
+Üblicherweise besteht die Suche aus zwei Elementen, die zusammen arbeiten: erstens, das Suchelement, das quasi ein Model ist, das konkret nach etwas sucht, und zweitens die QuickSearch-Klasse, die diese Suche ausführt und sich um die Ausgabe kümmert.
+
+```php
+$suche = new SQLSearch("SELECT username, Nachname " .
+ "FROM auth_user_md5 " .
+ "WHERE Nachname LIKE :input " .
+ "LIMIT 5", _("Nachname"), "username");
+print QuickSearch::get("username", $suche)
+ ->setInputStyle("width: 240px")
+ ->render();
+```
+
+Die Variable `$suche` ist also das Objekt, das die Suche durchführt. Dieses Objekt ist nicht notwendigerweise ein Objekt der Klasse SQLSearch, sondern ein Objekt der Klasse SearchType (wovon SQLSearch eine Unterklasse ist). SQLSearch ist nun eine spezielle Klasse, die beliebige SQL-Queries auf die Datenbank anwenden kann. In diesem Query bezeichnet `:input` stets den Suchstring, den ein Nutzer später eingibt.
+
+Die Klasse QuickSearch kümmert sich danach dann um die Ausgabe. Der erste Wert des Konstruktors ist immer der Name des Suchfeldes im HTML, also zum Beispiel `<input name="username">`. Der zweite Parameter ist dann das Suchobjekt, das wir vorher definiert haben. Danach folgen einige Methoden, um die Ausgabe weiter zu konfigurieren wie `setInputStyle("width: 240px")` und die Methode `render()` veranlasst dann die Ausgabe des Ganzen.
+
+Und so wird es dann aussehen:
+
+Attach:QuickSearch1.png Attach:QuickSearch2.png
+
+
+## Shortcut
+
+Für ganz einfache Suchen wie der Suche nach einem username oder einer user_id kann man als Suchobjektes auch einfach die Klasse "StandardSearch mit Parameter "username", "user_id", "Seminar_id", "Institut_id" oder "Arbeitsgruppe_id" schreiben. Also:
+
+```php
+print QuickSearch::get("seminar", new StandardSearch("Seminar_id"))
+ ->setInputStyle("width: 240px")
+ ->render();
+```
+
+## Weitere Methoden der QuickSearch-Klasse
+
+* *withButton()* : Das Suchfeld bekommt auch gleich eine Lupe dazu; diese Lupe ist ein einfacher Submit-Button. Vorteil ist einfach, dass man zum Design keine drei verschachtelten DIVs selber schreiben muss. Aber bitte benutzt diese Methode nicht in Kombination mit anderen Methoden unten. Dies ist quasi nur für ein ganz einfaches Suchfeld ohne spezielles Styling. Insbesondere setInputStyle zerschießt anschließend das Styling eher, als dass es es besser macht. Man kann nur die Länge der Box ändern durch withButton(array('width' => "50")) mit Pixeln als Längenangabe.
+* *defaultValue($valueID, $valueName)* : falls schon etwas eingetragen sein soll in dem Suchfeld, kann man hier den Namen und die dazu gehörige ID angeben.
+* *setInputClass($class)* : Name einer CSS-Klasse, die dem Feld mitgegeben wird.
+* *setInputStyle($style)* : besondere Angaben für style="" das dem Input-Feld mitgegeben wird.
+* *setDescriptionColor($color)* : Farbe der Beschreibung des Textfeldes. Die Beschreibung des Textfeldes taucht nur auf, solange der Nutzer noch nichts geschrieben hat und kann abweichen von der normalen Schreibfarbe des Textfeldes.
+* *noSelectbox()* : erzwingt, dass auf keinen Fall eine Select-Box für die Suchergebnisse angezeigt wird. Sinnvoll füre Suchfelder, die auf jeder Seite auftauchen. Aber Achtung! Hiermit fällt die nicht-JS Funktionalität des Suchfeldes ebenfalls flach.
+* *fireJSFunctionOnSelect($function_name)* : Der Programmierer kann eine Javascript Funktion angeben, die das ausgewählte Objekt weiter verarbeiten kann. Nur den Namen angeben. Die Javascript-Funktion sollte als Parameter (item_id, item_name) erwarten. Diese Funktion sollte true zurückliefern, damit das Ergebnis nach dem Abfeuern der JS-Funktion noch im Input bestehen bleibt. Ansonsten wird es automatisch wieder gelöscht.
+* *setAttributes($attr_array)* : Weitere Attribute für das Textfeld wie zum Beispiel ein title-Attribut. Zum Setzen solch eines titles würde `$attr_array = array('title' => 'nur ein Suchfeld')` übergeben werden. Natürlich funktioniert das aber auch mit "style" oder "class" als Attribut.
+* *disableAutocomplete($disable = true)* : Hiermit kann man das AJAX-Autocomplete für dieses Suchfeld deaktivieren. Meistens will man das zur Verbesserung der Performance tun. Man erhält dann kein Auswahlfeld mehr, sondern muss regulär auf Enter drücken und bekommt dann eine stinknormale Select-Box. Diese Eigenschaft deaktiviert nebenbei natürlich auch alle Angaben aus fireJSFunctionOnSelect und lässt sich nur bedingt mit noSelectbox kombinieren. Falls man alle Autocompleter für QuickSearches im System deaktivieren will, um die Performance zu verbessern, eignet sich die Config-Einstellung global -> AJAX_AUTOCOMPLETE_DISABLED besser, die bewirkt dasselbe. Nur eben global für das ganze System und ohne den Quellcode anfassen zu müssen.
+
+## Weitere Suchobjekte
+
+Man ist nicht auf SQLSearch beschränkt. Jeder Programmierer kann eigene Suchobjekte definieren und so zum Beispiel auch eine Lucene-Index-Suche implementieren, wenn ihm gerade danach ist. Die Suchklassen müssen alle von der Klasse SearchType abgeleitet werden und mindestens die Methoden `includePath()` und (sinnvollerweise) `getResults(...)` überschreiben. Falls die Suchklasse im Kern von Stud.IP benutzt wird, sollte sie auch im Verzeichnis `lib/classes/seachtypes/` hinterlegt werden. Aber Pluginbauer können ihre Suchklassen natürlich auch im Plugin hinterlegen.
+
+Eine kleine Beispielsuchklasse könnte zum Beispiel so aussehen:
+
+```php
+class SeminarTypSuchen extends SearchType {
+
+ public function getTitle() {
+ return _("Seminartyp suchen");
+ }
+
+ public function getResults($input, $contextual_data = array()) {
+ $typen = $GLOBALS['SEM_TYPE'];
+ foreach($typen as $key => $typ) {
+ if (strpos($typ['name'], $input) === false) {
+ unset($typen[$key]);
+ } else {
+ $typen[$key] = array($key, $typ['name']);
+ }
+ }
+ return $typen;
+ }
+
+ public function includePath() {
+ return __file__;
+ }
+}
+```
+
+Diese Klasse ist für den Fall gedacht, dass es in Stud.IP unübersichtlich viele Semeinartypen gibt. Diese Typen sind in der config.inc.php definiert und finden sich also nicht in der Datenbank. Für diesen Zweck ist also die Klasse SQLSearch unpraktikabel.
+
+Die Methode `getTitle` gibt nur den Schriftzug, der später im leeren Formularfeld stehen soll, wider.
+Die Methode `getResults` erledigt gewissermaßen die ganze Arbeit, durchsucht das Array aller Seminartypen nach dem eingegeben String und gibt ein Ergebnisarray der Form `array(array(ID_des_Seminar_Typs, Name_des_Typs), ...)` zurück.
+Die Methode `includePath` ist notwendig, damit diese Klasse gefunden wird (es gibt einen internen kleinen Autoloader), hat aber stets den gleichen Inhalt, kann also für alle Erweiterungsklassen von SearchType so übernommen werden.
+
+Und das sollte es auch gewesen sein. Man kann noch einen Avatar für seine Suchergebnisse angeben, was sich aber für Seminartypen nicht gerade anbietet. Dennoch würde man da tun, indem man die Methoden `getAvatar` und `getAvatarImageTag` überschreibt. Siehe dazu Dokumentation im Quellcode.
diff --git a/docs/docs/functions/responsive-navigation.md b/docs/docs/functions/responsive-navigation.md
new file mode 100644
index 0000000..447c818
--- /dev/null
+++ b/docs/docs/functions/responsive-navigation.md
@@ -0,0 +1,110 @@
+---
+id: responsive-navigation
+title: Responsive Navigation
+sidebar_label: Responsive Navigation
+---
+
+
+Ab Stud.IP 5.3 wurde die responsive Navigation komplett refaktorisiert und mir ihr auch ein Vollbildmodus für das gesamte System realisiert.
+
+# Benutzung
+In Stud.IP gibt es vier Stufen der auflösungsabhängigen Darstellung, diese sind in `resources/assets/stylesheets/scss/breakpoints.scss` (und analog in `resources/assets/stylesheets/less/breakpoints.less`) definiert und in `resources/assets/stylesheets/scss/visibility.scss` zur Steuerung von Sichtbarkeiten eingesetzt:
+- 576px (small)
+- 768px (medium)
+- 1024px (large)
+- 1280px (xlarge)
+- 1600px (xxlarge)
+
+Unterhalb einer Auflösung von 768 Pixeln wird automatisch zur responsiven Ansicht gewechselt und entsprechend die responsive Navigation verwendet.
+
+Darüber hinaus gibt es in der Desktopansicht (>= 768 Pixel) einen Button zum Wechsel in den Vollbildmodus. Hier wird ebenfalls die responsive Navigation und die angepasste Seitenansicht mit ausgeblendeter Sidebar und Contentbar über jeder Seite eingesetzt.
+
+# Technische Details
+Die responsive Navigation wurde mit VueJS implementiert. Alle neuen Komponenten liegen unter `resources/vue/components/responsive`.
+
+Die Komponente wird in `templates/header` eingebunden, aber nur bei Bedarf angezeigt. "Bei Bedarf" bedeutet hier:
+- Die Auflösung ist kleiner als 768 Pixel, damit bekommt das HTML-Tag automatisch die Klasse `responsive-display`
+- Der Vollbildmodus ist aktiv, damit bekommt das HTML-Tag automatisch die Klasse `fullscreen-mode`
+
+Beim Einhängen der Komponente wird ads Hilfeicon in die obere blaue Leiste verschoben, ebenso wird eine Contentbar erzeugt (oder eine bestehende im DOM umgehängt), um den aktuellen Seitentitel und das Icon zum Ein-/Ausblenden der Sidebar aufzunehmen.
+
+Das Icon zum Aktivieren des Vollbildmodus wird via MountingPortal neben das Hilfeicon eingehängt und wandert im Vollbildmodus ebenso in die blaue Topleiste.
+
+Die Einträge der Navigation werden in der Klasse `lib/classes/ResponsiveHelper.php` erzeugt und als JSON vorgehalten. Neu ist hier, dass alle eigenen Veranstaltungen aus dem aktuellen Semester (sowie ggf. eine gerade geöffnete Veranstaltung bei Admins und Roots) ebenfalls direkt als Navigationseinträge inkl. Unternavigation verfügbar sind.
+
+Die Fußzeile ist ausgeblendet, deren Navigation ist als Unternavigation "Impressum & Information" im Menü aufrufbar.
+
+Ebenso werden die Skiplinks der Seite umgebaut. Jeder Skiplink kann nun angeben, ob er im Vollbildmodus gültig ist (default `true`). Alle ungültigen Skiplinks werden beim Mounten der responsiven Navigation ausgeblendet und es werden neue eingebaut, die zur Navigation springen, den Vollbildmodus beenden oder das Icon zum Ein-/Ausblenden der Sidebar fokussieren.
+
+# Kompakte Navigatoin und Vollbildmodus
+
+Stud.IP ist eine Software, die sowohl auf verschiedenen Endgeräten/Gerätegrößen möglichst den vollständigen Funktionsumfang anzubieten versucht als auch für unterschiedliche Zielgruppen auf diesen verschiedenen Geräten möglichst gut bedienbar sein soll.
+
+**Unterstützte Geräteklassen und deren Kennzeichen:**
+
+Die Geräteklassen, auf die Stud.IP optimiert ist lassen sich wie folgt kategorisieren und entsprechend den Breakpoints im Resposiven Design.
+
+**A. Smartphone (medium)**: Diese Geräte sind dadurch gekennzeichnet, dass
+die maximale Breite sehr schmal ist (bis zu 767 Pixel bei regulärer Auflösung),
+üblicherweise das Scrollen leicht möglich ist, die Seiten also durchaus lang werden dürfen, die Geräte ausschließlich per Touch, also mit dem Finger bedient werden und auf diesen Geräten keine komplexen Inhalte erstellt werden. Die übliche Display-Ausrichtung ist hochkant.
+
+**B. Tablet/kleine Desktopgeräte (large)**: Diese Geräte sind dadurch gekennzeichnet, dass die maximale Breite begrenzt ist (bis zu. 1024 Pixel bei regulärer Auflösung),
+diese in der Regel per Touch bedient werden (Mausbedienung sollte ebenfalls möglich sein), auf diesen Geräten selten komplexe Inhalte erstellt werden. Die überwiegende Display-Ausrichtung ist quer, hochkant in einigen Anwendungsfällen.
+
+**C. Desktop (xlarge)**: Diese Geräte sind dadurch gekennzeichnet, dass die Breite mehr als 1024 Pixel aufweist und diese ganz überwiegend per Maus bedient werden. Die übliche Display-Ausrichtung ist quer.
+
+**weitere Varianten** Es gibt weitere Varianten, die bisher jedoch nur minimale Optimierungen auf die jeweiligen Gerätegrößen enthalten und weniger auf spezifischen Geräteklassen gestaltet wurden. Diese Varianten haben bei kleineren (unter 576 Pixel/small) bzw. größeren (ab 1280 Pixel/xlarge und ab 1600 Pixel/xxlarge) Displays Breakpoints.
+
+Siehe hierzu auch die neuen Darstellungsstufen unter https://gitlab.studip.de/studip/studip/-/wikis/Responsive-Navigation.
+
+**Exemplarische UseCases und zugeordnete Nutzendengruppen:**
+
+Neben verschiedenen Geräteklassen gibt es zwei wichtige Nutzendengruppen mit unterschiedlichen UseCases. Zwischen den Gruppen gibt es teils fließende Übergänge und Schnittmengen. Letztlich stellen beide Gruppen daher unterschiedliche Pole dar, für die es jeweils nun einen eigen Darstellungsmodus gibt. Beide Modus bauen dabei aufeinander auf.
+
+**1. Erstellung und Administration komplexer Inhalte**
+- Die üblichen Stud.IP-Gruppen für diesen UseCases sind **Admins** und (eingeschränkt) auch Lehrende
+- Der UseCase ist geprägt dadurch, dass umfangreich Inhalte erstellt oder komplexe Inhalte bearbeitet werden
+- typische genutzte Elemente sind große Tabellen (viele Elemente, viele Spalten, viele mögliche Aktionen) und umfangreiche Inhalte bestehend aus mehreren Medien-Objekten (Fließtext, Film, inaktive Elemente) die zudem in sich gegliedert sind (zB. durch ein Inhaltsverzeichnis oder Überschriften)
+- Die vollständige Bedienung (insbesondere Navigation) des Systems und Nutzung von Kommunikationsfunktionen wird weiterhin erwartet und bleibt möglich
+- Funktionen/Systembereiche können gewechselt, Aktionen der Sidebar ausgeführt und Kommunikationsfunktionen können aufgerufen werden
+- Wichtige Anforderungen: Möglichst viel Platz für die zu bearbeitenden Elemente bei gleichzeitig noch möglicher Navigation
+
+**2. Konsum und Interaktion mit Inhalten ohne diese zu verändern („Lernen“)**
+- Die üblichen Stud.IP-Rechtestufen dieser Gruppe sind **Studierende** und (eingeschränkt) auch Lehrende
+- Der UseCase ist geprägt dadurch, dass über einen längerer Zeitraum Inhalte rezipiert (Texte gelesen, Filme geschaut) werden
+- typische Elemente sind umfangreiche Fließtexte, Medienobjekte (Audio oder Video) und interaktive Elemente (Fragen, Quizzes, Prüfungen)
+- Die vollständige Bedienung tritt in den Hintergrund, üblicherweise wird über längere Zeit der gleiche Kontext dargestellt
+- Zentrales Ziel ist: Möglichst keine (optische) Ablenkung durch Elemente des Systems, die über eine längere Zeit nicht benötigt werden (dabei auch keine Ablenkung durch Interaktionselementen, die Aufmerksamkeit binden) bei gleichzeitig möglich viel Platz für die Interaktion
+- Es bleibt kein Platz für die gemeinsame Darstellung des Contents und der Bedienelemente/Navigation
+- Zentrale Anforderung: Ausblenden aller störenden oder ablenkende Elemente und möglichst viel Platz für den Content
+
+
+**Darstellungsmodus zur Unterstützung der beiden UsesCases**
+
+
+**I. Kompakte Navigation**
+
+Dieser Modus steht auf allen Seiten zur Verfügung, um die Nutzungsgruppe 1 (Admins/Lehrende) bei der Erstellung und Bearbeitung durchweg zu unterstützen. Der Modus ist auf alle Geräteklassen (A, B, C) optimiert.
+
+Kennzeichen der kompakten Navigation sind:
+
+- Die blaue Kopfzeile bleibt eingeblendet und ermöglicht Zugriff auf die vollständige Navigation („Hamburger-Menu“) in allen Geräteklassen
+- Der Browser selbst ist normal sichtbar (und damit auch alle anderen Fenster/Elemente des Betriebssystems)
+- Der Footer ist ausgeblendet, alle Navigationselemente des Footers sind Teil des Hamburgermenüs
+- Um möglichst viel Platz für die Bedienung zu schaffen, wird die Sidebar über ein Einblendicon sichtbar bzw. wieder unsichtbar, im Default ausgeblendet
+
+Anzumerken ist, dass bei langen Seiten auf den Geräteklassen B und C beim Herunterscrollen im normalen Modus die Responsive Navigation durch das Einblenden des Hamburger Menüs ebenfalls verwendet wird, um ein schnelles Wechseln ohne Hochscrollen weiterhin zu ermöglichen.
+
+**II. Vollbildmodus**
+
+Der UseCase 2 optimierte Fokusmodus kann in der Version 5.3 nur auf Seiten mit der neuen ContentBar (derzeit Courseware, Wiki und Material im OER-Campus) aktiviert werden, da davon ausgegangen werden kann, das dieser UseCase nur auf Seiten benutzt werden kann, auf denen aktiv Inhalte rezipiert werden. Der Vollbildmodus ist insbesondere auf die Klasse B (Tablets) optimiert, da davon ausgegangen wird, dass damit das Lernen und Lesen am besten funktioniert. Aber auch auf Desktop (C) ist der Modus nutzbar, wenn ggf. weniger sinnvoll.
+
+Kennzeichen des aktivierten Vollbildmodus sind:
+
+- Die blaue Kopfzeile wird ausgeblendet um sowohl maximalen Platz zu schaffen als auch die (versehentliche/aktive) Navigation zu unterbinden.
+- Der Browser-eigene Vollbildmodus wird ebenfalls aktiviert, da davon auszugehen ist, das auch keinerlei Ablenkungen anderer Tabs oder Bedienelemente bzw. versehentliches Antippen (auf Touchgeräten, Klasse A und B) zu verhindern. Alle anderen Fenster/Elemente des Betriebssystems werden damit (soweit wir möglich) ausgeblendet.
+- Die Sidebar wird ausgeblendet und kann nicht aktiviert werden
+- Der Footer ist nicht sichtbar
+- Die einzigen verbleibenden Bedienelemente außerhalb des Contents sind alle Elemente, die eine Bedienung innerhalb des Contents des gewählten Kontextes ermöglichen (zB. Inhaltsverzeichnis)
+
+Die Aktivierung des Vollbildmodus ist entweder nach Aktivierung der Kompakten Navigation als Icon ob rechts in der blauen Zeilen zu sehen (und stellt so auch einen zweiten Level der reduzierten Darstellung dar) oder kann in den Seiten, die den Modus nutzen, auch aus dem Aktionsmenü aktiviert werden.
diff --git a/docs/docs/functions/studip-form.md b/docs/docs/functions/studip-form.md
new file mode 100644
index 0000000..4399ed1
--- /dev/null
+++ b/docs/docs/functions/studip-form.md
@@ -0,0 +1,377 @@
+---
+id: studip-form
+title: Stud.IP-Formulare
+sidebar_label: Stud.IP-Formulare
+---
+
+In Stud.IP werden grundsätzlich HTML-Formulare verwendet. Das Stylesheet von Stud.IP bietet die Möglichkeit, dem Formular ein gewisses Standardaussehen aufzulegen. Dadurch fühlt sich ein Formular für die Nutzer immer irgendwie stimmig und optisch passend an.
+
+Uns ist dabei klar, dass Formulare nicht in ein starres Korsett gedrängt werden sollten. Was ist mit Drag&Drop Formularelementen? Was ist mit komplexen Multiselects, die Bilder enthalten sollen? Das sind Spezialfälle, die wir nicht vorhersehen können. Deshalb beschränkt sich das Stylesheet von Stud.IP auf die Grundelemente eines Formulars und versucht diese ansprechend zu gestalten. Für alles, was darüber hinaus geht, muss der Entwickler dann wieder selbst Hand anlegen.
+
+# Struktur
+
+```XML
+<form class="default">
+ <section>
+ <legend>Grunddaten</legend>
+ <label>
+ Name des Objektes
+ <input type="text">
+ </label>
+ <label>
+ Typ des Objektes
+ <select>
+ <option>Option 1</option>
+ <option>Option 2</option>
+ <option>Option 3</option>
+ </select>
+ </label>
+ <label>
+ <input type="checkbox">
+ Objekt sichtbar schalten
+ </label>
+ </section>
+</form>
+```
+
+Dies ist der grundlegende Aufbau eines HTML-Formulars in Stud.IP. Maßgeblich ist dabei vor allem die Klasse (class) des Formulars. Der Klassenname sollte dabei "default" sein. Diese Klassenname gibt dem Formular überhaupt erst das Aussehen, das es hat.
+
+Darunter kann man eine oder mehrere `fieldset`-Elemente definieren. Solche Sektionen sollen [laut HTML5](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset) verwendet werden, um mehrere Formularelemente zu gruppieren, die zusammen hängen. In Stud.IP bekommt so ein Fieldset einen blauen Rahmen und wird passend eingerückt.
+
+Unterhalb des `fieldset`-Elementes kann es ein `legend`-Element geben, das soetwas wie eine Überschrift für die `fieldset` beinhaltet. Man kann es auch weglassen, aber es sieht meist hübscher aus mit einem `legend`-Element.
+
+Jetzt kommen meist die eigentlichen Formularelemente, die jeweils durch ein `label`-Element umschlossen werden. Diese Labels sind enorm wichtig, um das Formular **barrierearm** zu halten. Denken Sie dabei immer an die Menschen, die zum Beispiel einen Screenreader verwenden, weil sie halb- oder ganz blind sind! Auch diese Menschen können studieren und müssen Stud.IP fast vollumfänglich benutzen können. In der Praxis ist es nicht leicht, sich in einen solchen Menschen mit eingeschränktem Sehvermögen hineinzuversetzen. Aber das müssen Sie als Programmierer auch nicht. Sie müssen nur daran denken: jedes Formularelement wie `<input>` oder `<textarea>` muss ein Label haben. Auch ein placeholder-Attribut an einem `input`-Element ist kein Ersatz für ein Label.
+
+Der Einfachheit halber schreiben Sie die `@<label>@s` wie oben angezeigt. Nur Radio-Buttons und Checkboxen sollten vor dem Label-Text stehen. Ansonsten steht der Labeltext vor dem Input-Element. Bei komplexen Eingabemöglichkeiten wie Komboboxen oder `<input>`s in einer Tabelle kann es sinnvoll sein, Label und Input-Element voneinander räumlich zu trennen. Das ist nicht verboten. Mit dem `for`-Attribut an dem Label können Sie dennoch Label-Text und `<input>` miteinander verbinden, sodass das Formular trotzdem barrierearm bleibt.
+
+# Besonderheiten / Goodies
+
+In Stud.IP haben wir bisher einige besondere Funktionen eingebaut, die einfach nett sind und dem Nutzer die Verwendung des Formulars einfacher machen.
+
+## Dateiauswähler
+
+Es ist leider unmöglich, einen `<input type="file">`-Dateiauswähler komplett umzustylen, dass er auf allen Geräten gleich aussieht - es sei denn, man lässt ihn verschwinden. Daher haben wir das genau so gemacht! Allerdings braucht es eine weitere CSS-Klasse, um das zu erzeugen. Schreiben Sie dafür solch eine Struktur
+
+```XML
+<label class="file-upload">
+ <input type="file">
+ <?= _('Neues Avatarbild hochladen') ?>
+</label>
+```
+
+In den meisten Fällen sollte der Dateiwähler dann recht gut aussehen und zu Stud.IP passen. Es wird ein zusätzliches Icon angezeigt und die ausgewählte Datei wird grau daneben angezeigt, damit der Nutzer sieht, ob er/sie irrtümlich eine falsche Datei ausgewählt haben könnte.
+
+## Einklappbare Fieldsets
+
+Nichts ist schlimmer als Unordnung. Gerade in sehr großen Formularen wird es manchmal hektisch. Die Fieldsets geben schon eine gute Ordnung vor. Aber eventuell will man ganze Fieldsets einklappen und erst zeigen, wenn das gewünscht ist. Das geht mit der zusätzlichen Klasse `collapsable`, die man entweder an das Fieldset hängen kann oder gleich an das ganze Formular, sodass es für alle Fieldsets darin gilt.
+
+```XML
+<form class="default collapsable">
+ <section>
+ <legend>Grunddaten</legend>
+ <label>
+ Name des Objektes
+ <input type="text">
+ </label>
+ </section>
+ <section>
+ <legend>Erweiterte Daten</legend>
+ <label>
+ Typ des Objektes
+ <select>
+ <option>Option 1</option>
+ <option>Option 2</option>
+ <option>Option 3</option>
+ </select>
+ </label>
+ <label>
+ <input type="checkbox">
+ Objekt sichtbar schalten
+ </label>
+ </section>
+</form>
+```
+
+## Maximale Textlänge
+
+Sowohl an `<textarea>` als auch an einem `<input>` kann man das Attribut `maxlength` anhängen. Der Browser beschränkt dann die Zeichenanzahl automatisch auf den angegebenen Wert. Stud.IP zeigt rechts unten des Formularelements zudem automatisch die verbleibenden Zeichen an, die einem bei der Eingabe noch bleiben. Die Anzeige der verbleibenden Zeichen kann durch die Angabe der CSS-Klasse `no-hint` unterdrückt werden.
+
+```XML
+<input type="text" maxlength="160">
+```
+
+## Horizontale Anordnung
+
+Stud.IP zeig die `<label>` Elemente und generell fast alles im Formular untereinander an. Das hat den Vorteil, dass wenig Platz in der Breite verwendet wird und das Formular sich sogar auf Smartphones gut benutzbar ist. Der Nachteil ist allerdings, dass man jetzt viel mehr scrollen muss als zuvor. Besonders bei Radio-Buttons mit wenig Text kann das absurd aussehen und nicht gewünscht sein. Bisher bietet Stud.IP dazu lediglich die Möglichkeit, ein Container-Element wie ein `<div class="hgroup">` einzubauen. In so einer `hgroup` werden alle Kindelemente horizontal angeordnet und nicht vertikal wie sonst.
+
+## Einfache Auswahl mehrerer Checkbox-Elemente [data-shiftcheck]
+
+Über das Attribut `data-shiftcheck` an dem Form-Element kann angegeben werden, dass es möglich sein soll, eine Menge von Checkboxen zu markieren, indem man die erste Checkbox aktiviert/deaktiviert, die Shift-Taste gedrückt hält und die letzte zu markierende Checkbox klickt. Alle Checkboxen zwischen der ersten und der letzten werden dann auf den Status gesetzt, den die letzte Checkbox erhält.
+
+# Weitere Möglichkeiten
+
+Nicht mit der CSS-Klasse `form` verbunden sind folgende Möglichkeiten:
+
+## Bestätigung einer Aktion [data-confirm]
+
+Über das Attribut `data-confirm` an einem Link, Button oder einem Formular können Sie sich eine Bestätigung der Aktion einholen. Der Wert des Attributes sollte die Frage sein, die dem Anwender gestellt wird.
+
+Beachten Sie, dass dies nicht die serverseitige Validierung der Eingaben ersetzen sollte. Weiterhin ist zu beachten, dass sämtliche Variablen, die in `data-confirm` gesteckt werden, mit `htmlReady()` bearbeitet werden müssen.
+
+## Datepicker [data-date-picker, data-time-picker, data-datetime-picker]
+
+Sie können einem `<input type="text">` die Möglichkeit geben, einen Datepicker zu bekommen, um Datumsangaben besser eingeben zu können, indem Sie dem Element das Attribut `data-date-picker` mitgeben. Analoges gilt für Timepicker (`data-time-picker`) bzw. Datetimepicker (`data-datetime-picker`).
+
+Bedenken Sie, dass HTML5 dafür eigentlich ein `<input type="date">` vorsieht. Dieses Element wäre normalerweise die bessere Alternative. Aber dabei gibt es leider Probleme, dass es in einigen Browsern nicht unterstützt wird. Daher kann man sich noch nicht darauf verlassen, dass die Eingabe auch immer gut funktioniert. Auf keinen Fall sollten Sie eine Kombination aus dem jQuery-Datepicker oben mit `<input type="date">` probieren. Bei Browsern, die `<input type="date">` verstehen, werden die Datepicker beide auftauchen und den Nutzer komplett verwirren.
+
+Der Wert des Attributs darf leer sein. Sie können aber auch für jedes der `date-...-picker`-Attribute ein JSON-Objekt als Wert angeben, in welcher Relation dieses Element zu einem anderen Objekt stehen soll. Es kann angegeben werden, ob der Wert des aktuellen Elements kleiner, kleiner gleich, größer gleich oder größer als der eines anderen Elements sein muss. Dies wird bei der Auswahl innerhalb des Pickers dann entsprechend berücksichtigt und die entsprechende Zeitspanne davor/danach ist nicht wählbar. Ebenso werden ungültige Werte in dem verbundenen Element angepasst.
+
+Die entsprechenden Elemente werden durch einen CSS-Selector angegeben. Folgend ein Beispiel für den Beginn der Veranstaltungszeit, welcher zwischen Semesterstart und Semesterende liegen muss:
+
+```XML
+<label>
+ <?= _('Semesterstart') ?>
+ <input type="text" name="semester-start" id="semester-start"
+ data-datepicker='{"<":"#semester-end"}'>
+</label>
+
+<label>
+ <?= _('Semesterende') ?>
+ <input type="text" name="semester-end" id="semester-end"
+ data-datepicker='{">":"#semester-start"}'>
+</label>
+
+<label>
+ <?= _('Vorlesungsbeginn') ?>
+ <input type="text" name="lecture-start" id="lecture-start"
+ data-datepicker='{">="semester-start","<":"#semester-end"}'>
+</label>
+```
+
+## Proxy-Elemente [data-proxyfor]
+
+Über das Attribut `data-proxyfor` an einer Checkbox kann ein CSS-Selector angegeben, der bestimmt, für welche anderen Checkboxen dieses Elements als "Proxy" dienen soll. So können mehrere Checkboxen über eine einzelne aktiviert oder deaktiviert werden.
+
+## Aktivieren/Deaktivieren von Elementen [data-activates, data-deactivates]
+
+Über das Attribut `data-activates` bzw. `data-deactivates` an einer Checkbox/Radiobox kann ein CSS-Selector angegeben werden, der bestimmt, welche anderen Element durch den Status dieses Elements aktiviert bzw. deaktiviert werden. `data-activates` kann auch an ein Select-Element gehängt werden und kann somit ein Element aktivieren sobald ein Wert ausgewählt wurde, der ungleich dem leeren String ist.
+
+An den Elemente, die so aktiviert bzw. deaktiviert werden sollen, kann der Status noch feiner gesteuert werden über das Attribut `data-activates-condition` bzw. `data-deactivates-condition`, welche einen CSS-Selector erwarten und den Status nur dann setzen, wenn dieser Selector mindestens einen Treffer hat.
+
+## Vergleich zweier Werte [data-must-equal]
+
+Über das Attribut `data-must-equal` an einem Element kann sichergestellt werden, dass die Werte in zwei Elementen identisch sind (beispielsweise bei einer Passworteingabe). An das zweite Bestätigungsfeld setzt man das Attribut mit einem CSS-Selector als Wert, der das Element bestimmt, welches identisch sein muss:
+
+```XML
+<label>
+ <?= _('Passwort') ?>
+ <input type="password" name="password" id="password">
+</label>
+
+<label>
+ <?= _('Passwort bestätigen') ?>
+ <input type="password" name="password-confirm" data-must-equal="#password">
+</label>
+```
+
+## Formsaver (data-confirm)
+
+Mit dem Attribut "data-secure" können Formulare oder Formularelementen durch Anzeige einer Warnung beim Verlassen der Seite geschützt werden, wenn es ungespeicherte Änderungen gibt.
+
+Fügen Sie das Datenattribut "data-secure" zu einem beliebigen "form"- oder "input"-Element hinzu und wenn die Seite neu geladen wird oder der umgebende Dialog geschlossen wird, erscheint ein Bestätigungsdialog.
+
+Es gibt zwei Konfigurationsoptionen für dieses Attribut:
+
+* always: Sichert das Element unabhängig von seinem Zustand. Wenn ein Formular immer gesichert werden soll, verwenden Sie diese Option. Wenn Sie ein Element von der Sicherheitsprüfung ausschließen möchten, setzen Sie dort den Attributwert auf "false" (aber Sie sollten die Kurzform `data-secure="false"` verwenden).
+
+* exists: Dynamisch hinzugefügte Knoten können nicht erkannt werden und werden daher niemals berücksichtigt, wenn sich deren Inhalt geändert hat. Geben Sie einen CSS-Selektor an, der präzise alle erforderlichen Elemente identifiziert.
+
+Diese Optionen können als json-kodiertes Array wie dieses übergeben werden:
+
+```XML
+<form data-secure='{always: false, existiert: "#foo > .bar"}'>
+```
+
+Da Sie aber wahrscheinlich nie beide Optionen gleichzeitig benötigen werden, können Sie entweder nur einen booleschen Wert an das "data-secure" Attribut zum Setzen der Option "always" oder einen anderen Nicht-Objektwert für die Option "exists" verwenden:
+
+```XML
+<form data-secure="true">
+```
+
+ ist gleichwertig mit
+
+```XML
+<form data-secure='{always: true}'>
+```
+
+und
+
+```XML
+<form data-secure="#foo .bar">
+```
+
+ ist gleichwertig mit
+
+```XML
+<form data-secure='{exists: "#foo .bar"}'>
+```
+
+# Die Form-Klasse (ab 5.2)
+
+In Stud-IP gibt es jetzt die Form-Klasse bzw. die Klasse heißt inklusive Namespaces `\Studip\Forms\Form`. Sie ist bestens geeignet, wenn man eines oder mehrere Objekte vom Typ `SimpleORMap` (SORM) hat, und diese bearbeiten bzw. speichern will. Aber die Form-Klasse kann auch Formulare darstellen und speichern, die nichts mit SORM-Objekten zu tun haben. In den meisten Fällen will man eine Mischung haben, auch das kann die Form-Klasse tun. Die Vorteile sind dabei auf einen Blick:
+
+* Als Programmierer:in musst Du Dir dabei in den meisten Fällen keine Gedanken zur Formvalidierung und Barrierefreiheit machen.
+* Es sieht fast immer gut aus und fügt sich perfekt in das Design von Stud.IP ein.
+* Die Standardfälle von Inputs lassen sich einfach behandeln.
+* Und für die Spezialfälle und in Plugins kann man die Klasse entsprechend mit eigenen Input-Klassen erweitern.
+
+## Interner Aufbau des Formulars
+
+Ein Formular besteht aus mehreren Input-Elementen und den Strukturelementen wie einem Fieldset (die blauen Kästen um die Eingabefelder) oder einer H-Group (ein `<div class="hgroup">`, in dem alle Inputs nebeneinander bzw. horizontal angeordnet werden). Solche Strukturelemente können auch beliebig verschachtelt werden. Sehr beliebt ist es zum Beispiel, eine H-Group in ein Fieldset einzubauen.
+
+Wenn das Form-Element dann irgendwann alle Inputs haben möchte, etwa um sie zu speichern, holt es sich das mit der Methode `getAllInputs`, mit der alle Parts rekursiv durchsucht werden nach Input-Elementen.
+
+Jedes Input Element ist damit von einer von der abstrakten Klasse `\Studip\Forms\Input` abgeleiteten Klasse. Jedes Input Objekt kennt seinen Namen im Formular, hat eventuell sogar noch Funktionen, die aufgerufen werden beim Speichern oder für das Data-Mapping.
+
+## Beispiele
+```php
+ $form = \Studip\Forms\Form::fromSORM(
+ User::findCurrent(),
+ [
+ 'without' => ['password', 'chdate', 'user_id'],
+ 'types' => ['lock_comment' => 'datetimepicker'],
+ 'legend' => _('Nutzerdaten von mir')
+ ]
+ )->setURL($this->url_for('mycontroller/save'));
+```
+
+Man könnte das auch anders schreiben:
+
+```php
+ $form = \Studip\Forms\Form::fromSORM(
+ User::findCurrent(),
+ [
+ 'fields' => [
+ 'username' => _('Anmeldekennung'),
+ 'vorname' => _('Vorname'),
+ 'nachname' => _('Nachname'),
+ 'email' => _('Emailadresse'),
+ 'lock_comment' => [
+ 'label' => _('Sperrdatum'),
+ 'type' => 'datetimepicker'
+ ]
+ ],
+ 'legend' => _('Nutzerdaten von mir')
+ ]
+ )->setURL($this->url_for('mycontroller/save'));
+```
+In diesem Beispiel wird der angemeldete Nutzer bearbeitet. In dem Array gibt es den Eintrag `fields`, in dem die Felder, die im Formular dargestellt werden sollen, benannt werden. In diesem Fall ist der Index gleich dem Feldnamen im SORM-Objekt aber auch dem Input-Namen im Formular bzw. im Request. Und der Value ist entweder ein Array mit Attributen oder in Kurzschreibweise einfach nur ein String, der dann dem sichtbaren Label des Feldes entspricht.
+
+Die Form-Klasse analysiert die Datenbank und versucht, die meisten Angaben zu vervollständigen, was den `type` angeht zum Beispiel. Die Idee ist, dass man als Programmierer möglichst wenige Angaben machen muss, um ein schickes Formular zu bekommen. Aber die Spezialfälle machen es aus.
+
+Es gibt auch noch eine objektorientierte Variante, um die Angaben zu machen, die dann so aussehen würde:
+
+```php
+ ...
+ 'Vorname' => \Studip\Form\TextInput::create(
+ 'Vorname',
+ _('Vorname'),
+ User::findCurrent()->vorname
+ )->setRequired()
+ ...
+```
+Diese Variante hat den Vorteil, dass die IDE weiß, was der Typ (bzw. `type`) des Formularfeldes ist, und man wie mit setRequired weiter arbeiten kann. Die Array-Notation ist zwar schlanker und übersichtlicher, aber man muss wissen, was die Parameter bedeuten. Grundsätzlich wird aus dem beispiel `type` in der Array-Schreibweise ein Objekt vom Typ `\Studip\Form\BeispielInput` - dabei ist es egal, ob die Klasse `BeispielInput` im Kern von Stud.IP vorhanden ist oder von einem Plugin bereitgestellt wird. Auf diese Weise kann ein Plugin auch eigene Formularfeldtypen mitbringen und anzeigen. So eine Klasse `BeispielInput` wäre dann abgeleitet von der abstrakten Klasse `\Studip\Form\Input`.
+
+## Die Formularfeldtypen im Kern
+
+Die folgenden Klassen liegen alle unter `lib/classes/forms` bzw. im PHP-Namespace `\Studip\Form`.
+
+**TextInput**: Dies ist der gängigste Input-Typ, der einfach einem `<input type='text'>` ohne große Schnörkel entspricht. Dennoch gibt es ein paar mögliche Angaben, die man bei allen Input-Klassen machen kann:
+
+1. Zu Erwähnen wäre da das obige setRequired (in der objektorientierten Version) bzw. `'required' => true` als Array-Parameter, um zu sagen, dass die Angabe dieses Feldes nicht leer sein darf. Eine Checkbox, die `required` ist, muss angehakt sein.
+2. Der Parameter `'permission' => $GLOBALS['perm']->have_perm('admin)`, mit dem man definiert, dass dieses Formularfeld nur angezeigt und ausgewertet werden darf, sofern hier ein `true` eingegeben wird, man also die Permission dazu hat.
+3. Der Parameter `if` bzw. die Methode `setIfCondition`, mit dem man definiert, dass dieses Formularfeld nur angezeigt werden soll, sofern eine Bedingung erfüllt ist. Diese Bedingung wird immer per Javascript überprüft, während das Formular ausgefüllt wird. So könnte man eine Checkbox anzeigen, und nur wenn diese Checkbox angehakt ist, tauchen weitere Formularelemente auf. Dieser Parameter `if` ist nicht dazu da, dass eine Validierung durchgeführt wird oder ein Sicherheitscheck, sondern ausschließlich zu Zwecken der Übersichtlichkeit des Formulars. In dieser Bedingung kann auch eine Javascript-Auswertung stehen wie `'if' => 'age > 18'`. Das Formular kennt im Javascript dann die Werte der anderen Formularfelder, wie sie gerade ausgefüllt worden sind, und zeigt dementsprechend die Felder an.
+4. Mit dem Parameter `value` bzw. dem Aufruf im Konstruktor der `\Studip\Form\Input`-Klasse setzt man den Wert des Formularfeldes. Normalerweise wird das der Wert des SORM-Objektes sein, aber es könnte auch ein ganz anderer Wert gesetzt werden. Falls das Formular gar nichts mit einem SORM-Objekt zu tun hat, wäre das natürlich sogar notwendig. meist wird das aber nur bei einzelnen Formularfeldern gesetzt.
+5. Mit dem Parameter `store` bzw. der Methode `setStoringFunction` kann man eine PHP-Funktion einsetzen, die sich dann um das Speichern der Formularwerte kümmert, wenn das Formular abgeschickt worden ist. Falls das Formular ein SORM-Element speichert, ist diese Funktion immer so gesetzt, dass der Wert der SORM-Klasse gespeichert wird. Hier müssen Sie also nichts weiter definieren. Man könnte aber hier auch eine Funktion definieren, die einen Wert zum Beispiel in die UserConfig schreibt.
+6. Mit dem Parameter `mapper` bzw. der Methode `setMapper` kann man eine Funktion definieren, die den Wert aus dem Formular umwandelt, bevor dieser gespeichert wird. So könnte man aus einer schriftlichen Datumsangabe '7.3.2012' zum Beispiel einen Unix-Timestamp (Anzahl der Sekunden seit 1970) umwandeln, als der er dann in der Datenbank gespeichert wird (aber dies ist ein schlechtes Beispiel, weil der Typ datetimepicker die Umwandlung schon in Javascript macht und nur der Unix-Timestamp übertragen wird und nicht die lesbare Entsprechung des Datums). Die angegebene Mapper-Funktion bekommt als Parameter zuerst den Wert aus dem Formular übergeben und als zweiten Parameter das SORM-Objekt, sofern denn eines existiert. Oft sieht man den `mapper` Parameter in Kombination mit dem `'type' => 'no'`, dem `NoInput`. Das ist ein Eingabefeld, das dem Benutzer gar nicht angezeigt wird, aber die Mapper-Funktion schreibt beim Speichern trotzdem den Wert der Mapper-Funktion in die Datenbank wie zum Beispiel den Namen der bearbeitenden Person.
+
+So, nun aber wirklich zu der Liste der Input-Klasse, die es im Kern so gibt (in alphabetischer Reihenfolge):
+
+**CalculatorInput**: Dies ist streng genommen kein Eingabefeld, sondern nur ein Ausgabefeld. Hier wird eine Berechnung von Werten angegeben. Beim Bearbeiten der Ankündigung gibt dieses Feld per Javascript dem Nutzer aus, wieviele Tage zwischen Beginn der Ankündigung und deren Ende liegen, sodass man sich bei der Eingabe sicher sein kann, zum Beispiel mindestens 14 Tage dazwischen zu haben. In dem Parameter `'value' => "Math.floor((expire - date) / 86400)"` steht dann eine Javascript-Formel, die vom Formular permanent während des Ausfüllens ausgewertet wird. Hier sind einfache Angaben in Javascript machbar aber keine Steuerungsangaben wie While-Schleifen.
+
+**CheckboxInput**: Hier wird ein `<input type='checkbox'>` dargestellt. Der `value` lautet 1 oder 0, wie er auch in der Datenbank eingetragen wird.
+
+**DatetimepickerInput**: Dieses Feld ist eine Datums- und Zeitangabe und entspricht einem Unix-Timestamp. In Stud.IP nutzen wir fast überall Unix-Timestamps, die Anzahl der Sekunden vom 1.1.1970 Greenwich Zeit), um ein Datum in der Datenbank zu speichern. Falls in der Datenbank ein anderes Datumsformat wie ISO-Datum speichern will, sollte man die Parameter folgendermaßen setzen: `'value' => strtotime($obj['zeitfuermich'])` und `'mapper' => function ($val) { return date('c', $val); }`.
+
+**HiddenInput**: In diesem Feld ist zwar ein Wert im Formular vorhanden, aber man sieht nichts und kann auch nichts eingeben. Vermutlich will man den Wert lediglich nutzen, damit man ihn in anderen Formularelementen in der `if` Klausel oder mit dem CalculatorInput auswerten kann.
+
+**I18n_formattedInput**: Mit dieser Input-Klasse wird ein WYSIWYG-Editor angezeigt - allerdings in mehreren Sprachen, sofern in der `config_local.inc.php` Datei entsprechend weitere `$CONTENT_LANGUAGES` eingetragen worden sind. Diese Klasse kümmert sich dabei um die `$CONTENT_LANGUAGES` und die Frage, ob sie eingetragen worden sind und wie viele. Falls nämlich keine weiteren `$CONTENT_LANGUAGES` eingetragen wurden, wird auch gar kein Sprachwähler angezeigt, wie es in Stud.IP üblich ist. Und wenn der WYSIWYG-Editor in dem Stud.IP ausgeschaltet ist, wird auch nur ein normales Textfeld mit Toolbar abgezeigt. Das macht diese Klasse alles automatisch. Das entsprechende Feld der SORM-Klasse **muss** ebenfalls als i18n Feld deklariert werden in der configure-Methode in dieser Form `$config['i18n_fields']['feldname'] = true;`.
+
+**I18n_textareaInput**: Diese Klasse stellt ein `<textarea>` dar, das wie gerade eben auch eventuell einen Sprachwähler hat, sodass man den Wert in mehreren Sprachen eingeben kann. Wenn man eine SORM-Klasse bearbeitet, wird diese Klasse automatisch als `type` ausgewählt, wenn in der Datenbank das Feld den Typ `TEXT` und die SORM-Klasse in der configure-Methode sowas stehen hat wie: `$config['i18n_fields']['feldname'] = true;`
+
+**I18n_textInput**: Diese Klasse stellt ein `<input type='text'>` dar, das wie gerade eben auch eventuell einen Sprachwähler hat, sodass man den Wert in mehreren Sprachen eingeben kann. Wenn man eine SORM-Klasse bearbeitet, wird diese Klasse automatisch als `type` ausgewählt, wenn in der Datenbank das Feld den Typ `VARCHAR` und die SORM-Klasse in der configure-Methode sowas stehen hat wie: `$config['i18n_fields']['feldname'] = true;`
+
+**InputRow**: Dies ist eigentlich keine Input-Klasse, sondern eine Erweiterung der Klasse `\Studip\Form\Part`. Wie dem auch sei, man kann mit dieser Klasse mehrere Eingabefelder horizontal gruppieren. Die Angabe dazu würde in etwa so aussehen:
+
+ 'row' => new \Studip\Forms\InputRow(
+ [
+ 'name' => 'feld1',
+ 'label' => _('Feld 1')
+ ],
+ [
+ 'name' => 'feld2',
+ 'label' => _('Feld 2')
+ ],
+ [
+ 'name' => 'feld3',
+ 'label' => _('Feld 3')
+ ]
+ )
+Man sieht, man muss hier bei den Eingabefeldern noch den Parameter `name` mit angeben, der ansonsten der Index ist. Ansonsten kann man an den Konstruktor von `InputRow` eine beliebige Anzahl von Eingabefeldern bzw. Input-Objekten übergeben, die dann nebeneinander angezeigt werden.
+
+**MultiselectInput**: Dies ist ein Input, mit dem man üblicherweise eine Relation eines SORM-Objektes anlegt. Besonders ist hier eigentlich nur, dass im Request an PHP bzw. im $value der Mapper-Methode und der Store-Methode ein Array drin steht anstatt eines Strings, wie es sonst üblich ist. Falls man eine SORM-Relation damit bearbeiten will, sollte man die Parameter `value` und `mapper` (oder `store`) mit setzen, damit das auch funktioniert. In SORM selbst braucht man eigentlich nur eine SimpleORMapCollection als den neuen Wert des SORM-Objektes zu setzen. Die SimpleORMap-Klasse weiß dann schon selbst, welche Objekte hinzugefügt und welche gelöscht werden müssen. Ein Beispiel aus dem Bearbeiten der Ankündigung ist folgender:
+
+```php
+ 'newsroles' => [
+ 'permission' => $GLOBALS['perm']->have_perm('admin'),
+ 'label' => _('Sichtbarkeit'),
+ 'value' => $news->news_roles->pluck('roleid'),
+ 'type' => 'multiselect',
+ 'options' => array_map(function ($r) {
+ return $r->getRolename();
+ }, RolePersistence::getAllRoles()),
+ 'store' => function ($value, $input) {
+ $news = $input->getContextObject();
+ NewsRoles::update($news->id, $value);
+ }
+ ]
+```
+
+Über den Parameter `options` wird gesteuert, welche Auswahlmöglichkeiten der Benutzende hat.
+
+**NewsRangesInput**: Diese Input-Klasse ist sehr spezifisch für die Ankündigungen und weist die Ankündigung den Bereichen in Stud.IP zu wie der Startseite, den Einrichtungen, Veranstaltungen und persönlichen Homepages. Vermutlich wird niemand jemals diese Klasse für etwas anderes als zum Bearbeiten von Ankündigungen verwenden können. Aber das zeigt auch, dass der Formularbaukasten auch mit sehr speziellen Formularelementen umgehen kann. Und innerhalb der NewsRangesInput gibt es immerhin eine Vue-Komponente `EditableList`, die man wiederverwenden kann.
+
+**NoInput**: Hier gibt es nichts zu sehen! Stimmt, es wird eigentlich nur ein `<input type='hidden'>` in dem Formular platziert. Man kann dieses nutzen, um Auswertungen in Javascript für `if`-Bedingungen oder für den CalculatorInput zu ermöglichen. Und wer weiß, was man noch damit so anstellen kann?
+
+**NumberInput**: Hiermit wird einfach eine Ganzzahl angegeben. Sinnvoll ist hier auch oft die Angabe von anderen HTML-Attributen wie min oder max, wobei da auch der RangeInput zum Einsatz kommen könnte. Aber wenn nicht, würde man diese Attribute als weitere Parameter angeben wie `'max' => 20'`.
+
+**QuicksearchInput**: Mit diesem Eingabefeld hat man eine Quicksearch wie die PHP-Klasse Quicksearch. Der Zweck ist, dass zwar in der Datenbank (meist) eine ID eingetragen werden soll, aber die Benutzer wollen keine ID schreiben. Stattdessen geben Sie den Namen einer Veranstaltung oder einer Person oder sonstwas ein und die Quicksearch setzt dann die ID als Wert in das Formular. Damit das Ganze funktionieren kann, muss man zusätzlich den Parameter `'searchtype' => new SQLSearch(...)` oder ähnlich angeben. Also ohne den Searchtype gibt es keine Suche und damit keine Quicksearch.
+
+**RangeInput**: Hiermit können Ganzzahlen zwischen `max` und eventuell `min` (Standard sind zwischen 1 und 10) eingegeben werden. Die Eingabe erfolgt mittels eines Schiebereglers und der aktuelle Wert steht dann daneben.
+
+**SelectInput**: Mit dieser Klasse wird ein `<select>` im HTML verwendet. Die darin liegenden Optionen werden wie schon im MultiselectInput über den Parameter `'options' => ['value1' => _('Der erste Wert'), 'value2' => _('Der zweite Wert')]` gesteuert. Wenn in der Datenbank ein `ENUM`-Feld steht, so wird ohne weitere Angaben von `type` oder `options` ein SelectInput mit den Werten des ENUMs erzeugt.
+
+**TextInput**: Siehe oben.
+
+**TextareaInput**: Eine einfache Klasse zum Erzeugen einer `<textarea>`. Diese funktioniert ganz analog zu TextInput.
+
+## Bauen einer eigenen Input-Klasse
+
+Wenn selbst ein Formular baut, stellt man schnell fest, dass die bisherigen Input-Klassen viel abdecken, aber recht oft dann doch notwendig ist, dass man hier und da mal ein Spezialeingabefeld hat. Für diesen Fall lassen sich auch neue Input-Klassen programmieren - entweder für ein Plugin oder für den Kern von Stud.IP. Folgende Dinge sollte man dabei beachten:
+
+- Im Wesentlichen muss die eigene Input-Klasse von der abstrakten Klasse `\Studip\Form\Input` abgeleitet sein und zumindest die Methode `render` überschreiben. Mit der Methode `render` wird die HTML-Struktur ausgegeben, die am schlussendlich im Formular stehen soll.
+- Das Element wird dann eingebettet in einer Vue-Instanz. Das bedeutet, das HTML des Eingabeelementes kann Vue-Tags wie v-model verwenden und sollte das auch tun. **Es sollte der Wert des Eingabefeldes immer sowohl im HTML (per `<input type='hidden'>` oder ähnlich) stehen als auch per v-model oder anderen mechanismen an die Vue-Instanz übergeben werden.** Immer ist beides notwendig, damit das Eingabefeld der eigenen Input-Klasse voll nutzbar ist - zum Beispiel indem man mit dem `if`-Parameter anderer Eingabefelder arbeiten kann, mit dem CalvulatorInput und mit der Formular-Validierung.
+- Man kann in dem HTML des Eingabefeldes daher natürlich auch Vue-Komponenten verwenden. PHP-Klassen wie Quicksearch in PHP oder jQuery sollte man hingegen meiden, wie der Teufel das Weihwasser, weil Vue im Zweifelsfall zum falschen Zeitpunkt neu gerendered wird und dann das schöne PHP oder jQuery kaputt machen. Falls man Vue-Komponenten verwendert, muss man natürlich sicher stellen, dass diese auch korrekt geladen sind.
+- Wenn im Request andere Dinge als ein String (oder sowas ähnliches wie ein String wie eine Ganzzahl) drin steht, sollte man die Methode `getRequestValue` überschreiben, die dann meist sowas wie `return \Request::getArray($this->name);` ausführt. Das bedeutet, die Klasse weiß, dass sie ein Array (oder etwas ähnliches) aus dem Request erwartet. Die `mapper`-Methode kann dann danach noch verwendet werden, um die Ausgabe der Methode `getRequestValue` zu verändern. Dadurch bleibt die Input-Klasse flexibel im Einsatzzweck.
+
+Mehr ist dazu gar nicht zu sagen. Vermutlich muss man einfach ein bisschen was ausprobieren und aus den Beispielen im Kern abschreiben.
diff --git a/docs/docs/functions/studip-format.md b/docs/docs/functions/studip-format.md
new file mode 100644
index 0000000..1b5d882
--- /dev/null
+++ b/docs/docs/functions/studip-format.md
@@ -0,0 +1,96 @@
+---
+id: studip-format
+title: StudipFormat
+sidebar_label: StudipFormat
+---
+
+:::danger
+Die Funktion ist veraltet und sollte nicht mehr verwendet werden
+:::
+
+Zu der Stud.IP-Version 2.3 wurde die Formatierungsengine umgestellt auf einen eigenen Parser, der sich in den Klassen `StudipFormat` und `TextFormat` verbirgt. TextFormat ist dabei der grundsätzliche Parser und StudipFormat ein spezieller, der mit den typischen Studip-Syntax angereichert ist.
+
+Die Benutzung ist grundsätzlich einfach. Man instanziiert den Parser und schickt seinen zu formatierenden Text durch die Methode `format`. Etwa so:
+
+```php
+$markup = new StudipFormat();
+echo $markup->format("Mein %%cooler%% formatierter Text ist **cool**.");
+```
+
+
+Man kann auch einen eigenen Parser definieren mit der Klasse `TextFormat`. Ein nacktes Objekt davon macht allerdings erst einmal gar nichts denn `TextFormat` bringt von sich aus keine Regeln mit. Eine Regel definiert man über den Konstruktor oder danach über die Methode `addMarkup`. Etwa so:
+
+```php
+function bbbold($text) {
+ return "<b>".$text."</b>";
+}
+function bbitalic($text) {
+ return "<i>".$text."</i>";
+}
+$bbmarkup = new TextFormat(array(
+ 'bold-text' => array(
+ 'start' => "\[b\]",
+ 'end' => "\[\/b\]",
+ 'callback' => "bbbold"
+ )
+));
+$bbmarkup->addMarkup(
+ 'italic-text',
+ "\[i\]",
+ "\[\/i\]",
+ "bbitalic"
+);
+```
+
+
+Innerhalb von Stud.IP werden immer die Funktionen `formatReady` `linkReady` und `wikiReady` verwendet, um Text zu formatieren. Mit dem neuen Parser kann man jetzt auch Plugins bauen, die dieses Markup jeweils erweitern bzw. verändern. `htmlReady` verwendet intern einfach `$markup->format($text)`, um den Text zu formatieren, die wiederum eine Reihe von Formatierungsregeln voreingestellt hat. Diese Formatierungsregeln stehen in der privaten Variable `StudipFormat->studip_rules`, die sich mit den statischen Methoden `getStudipMarkup($name)`, `addStudipMarkup($name, $start, $end, $callback, $before = null)` und `removeStudipMarkup($name)` manipulieren lässt. Am interessantesten ist da sicherlich die Methode `addStudipMarkup`.
+
+Man könnte zum Beispiel ein Plugin bauen, das das Markup für Stud.IP erweitert. Ich stelle mal ein kleines Beispielplugin vor, das OpenstreetMaps überall einbinden, wo der Nutzer sowas schreibt wie `[osmap]latitude;longitude;zoomfactor[/osmap]` Das ist simpel und kann man immer gut gebrauchen.
+
+```php
+class OpenstreetmapEmbedder extends StudIPPlugin implements SystemPlugin {
+
+ public function __construct()
+ {
+ parent::__construct();
+ StudipFormat::addStudipMarkup("osmap", "\[osmap\]", "\[\/osmap\]", "OpenstreetmapEmbedder::createMap");
+ PageLayout::addHeadElement('script', array('type' => "text/javascript", 'src' => $this->getPluginURL()."/assets/khtml_all.js"), *);
+ }
+
+ public static function createMap($markup, $matches, $adress)
+ {
+ $id = "map_".uniqid();
+ list($latitude, $longitude, $zoom) = explode(";", $adress);
+ $output = sprintf(
+ '<style>#%s > div:not(:first-child) {display: none;} </style>'.
+ '<div id="%s" style="width: 400px; height: 300px; border: 1px solid black;"></div>' .
+ '<script>
+ jQuery(function () {
+ map=new khtml.maplib.base.Map(document.getElementById("%s"));
+ map.centerAndZoom(new khtml.maplib.LatLng(%s,%s),%s);
+ var zoombar=new khtml.maplib.ui.Zoombar();
+ map.addOverlay(zoombar);
+ });
+ </script>',
+ $id,
+ $id,
+ $id,
+ (double) $latitude,
+ (double) $longitude,
+ (int) $zoom
+ );
+ return str_replace("\n", "", $output);
+ }
+}
+
+```
+
+Wenn man mal von den Eigenheiten der zugrundeliegenden Javascript-Engine zur Darstellung der Karte absieht, ist das Plugin ganz simpel: Im Konstruktor wird das Markup über die Methode `StudipFormat::addStudipMarkup` an StudipFormat angegeben. Damit existiert das Markup schon mal. Der erste Parameter ist einfach ein Name, der frei gewählt werden kann. Der zweite Parameter ist der Startausdruck als regulärer Ausdruck. Hier könnten auch benannte Ausdrücke wie `(.*?)` drin vorkommen, die nachher per `$matches` Variable an die ausführende Markup-Funktion übergeben werden. Aber für unser Beispiel ist das egal, da wir nur einen Parameter brauchen.
+Das dritte Argument ist der Endausdruck. Alles zwischen Startausdruck und Endausdruck bekommt die Funktion später als `$contents` übergeben.
+Das vierte Argument ist der Name der Funktion, die später das Markup ausführen soll. Statische Funktionen eignen sich dafür am besten.
+Es könnte noch ein fünftes Argument geben, das `$before` heißt und angibt, vor welchen anderen Direktiven dieses Markup ausgeführt wird. Es könnte ja sein, dass zwei Markups Doppeldeutigkeiten hervorrufen. Und da 'gewinnt' immer das Markup, das weiter vorne in der Reihenfolge steht. Gibt man `$before = "links"` mit, so wird das eigene Markup immer der Linksyntax vorgezogen.
+
+In der Methode `OpenstreetmapEmbedder::createMap`, der Methode, die das Markup ausführt, bekommen wir drei Parameter, wovon der dritte nur existiert, wenn ein Endausdruck definiert wurde. Der erste Parameter ist immer ein `TextFormat`-Objekt, das gerade das Markup koordiniert. In den meisten fällen, braucht man dieses Objekt nicht zu beachten. `$matches` ist ein Array mit allen Parametern, die man aus dem regulären Ausdruck gewinnt. `$matches[0]` ist stets der ganze String, `$matches[1]` wäre schon der erste benannte Parameter im regulären Ausdruck wie `(.*?)`. `$contents`, das dritte Argument und bei unserem Beispiel gleich `$adress` genannt, gibt alles zwischen Startasudruck und Endausdruck an.
+Die Methode `createMap` muss jetzt nur noch etwas zurückgeben, das praktisch für `$matches[0]` eingesetzt wird, also für den betroffenen String. Der hier angefasste String wird überdies nie mehr von anderem Markup beeinflusst.
+
+**WARNUNG** für Pluginentwickler: Seid euch bewusst, dass ihr zwar schnell ein Plugin bauen könnt, das die Leute vermutlich auch schnell verstehen. Aber die Syntax der Formatierung, die ihr wählt, ist von Anfang an für alle Zeiten fest, denn die Forumsbeiträge, die eure neue Syntax verwenden, werden auf Ewigkeiten die Syntax behalten, die ihr anfangs mal verwendet habt. Überlegt euch daher die Syntax gut, geht sparsam mit Formatierungsmöglichkeiten um, von denen absehbar ist, dass sie eines Tages veraltet sein werden (wie SWF-Datei-Integration), oder wo der Datenschutz mindestens strittig ist (wie eine Google-Maps-Integration).
diff --git a/docs/docs/functions/studip-mail.md b/docs/docs/functions/studip-mail.md
new file mode 100644
index 0000000..9df7543
--- /dev/null
+++ b/docs/docs/functions/studip-mail.md
@@ -0,0 +1,65 @@
+---
+id: studip-mail
+title: Stud.IP-Mail
+sidebar_label: Stud.IP-Mail
+---
+
+Diese Klasse [StudipMail](https://gitlab.studip.de/studip/studip/-/blob/main/lib/classes/StudipMail.class.php) bietet Möglichkeiten eine Email zu erzeugen und zu versenden. Der Versand der Nachricht wird nicht direkt von der Klasse vorgenommen, sondern von einer Instanz einer weiteren Klasse für den Emailtransport. Die dazu benutzen Klassen befinden sich in `vendor/email_message`, hier gibt es verschiedene Möglichkeiten für den Transport z.B. über die php Funktion Mail oder über smtp.
+
+
+## schnelles Beispiel
+
+```php
+$mail = new StudipMail();
+$mail->addRecipient('suchi@data-quest.de', 'Stefan Suchi')
+ ->addRecipient('elmar.ludwig@uos.de', 'Elmar Ludwig', 'Cc')
+ ->setSubject('Test der Mail Klasse')
+ ->addStudipAttachment($dokument_id)
+ ->setBodyText("Hallo,\nBlablubb")
+ ->send();
+```
+
+## Mail Transport
+Welcher Transport für die Mail benutzt wird, kann ich der `config/config_local.inc.php` bei den Mail-Einstellungen mit $MAIL_TRANSPORT konfiguriert werden. Hier stehen zur Auswahl:
+
+| Art | Beschreibung |
+| ---- | ---- |
+| `smtp` | direkte smtp Verbindung zu `$MAIL_HOST_NAME` |
+| `php` | php `mail()` Funktion wird genutzt |
+| `sendmail` | direktes Aufrufen des lokalen sendmail skriptes |
+| `qmail` | direktes Aufrufen des lokalen qmail skriptes |
+| `debug` | mails werden nicht verschickt sondern in eine Datei in `$TMP_PATH` geschrieben |
+| `blackhole` | mails werden nicht verschickt, sondern einfach verworfen |
+
+Aufgrund dieser Einstellung wird automatisch in `lib/phplib_local.inc.php` eine Instanz der entsprechenden Transporterklasse erzeugt und mit `StudipMail::setDefaultTransporter($mail_transporter)` als Standardweg zum verschicken hinterlegt.
+
+Es ist aber möglich dieses Objekt auszutauschen, entweder über den obigen Aufruf von `StudipMail::setDefaultTransporter()` oder aber beim Aufruf von StudipMail::send(), da die `send()` Methode als optionalen Parameter ein von `email_message_class` abgeleitetets Objekt akzeptiert.
+
+Damit ist es auch möglich den automatischen Versand von Mails temporär zu verhindern, wie es z.B. die Klasse `UserManagement` bei Änderungen eines Nutzers macht:
+
+```php
+require_once 'vendor/email_message/blackhole_message.php';
+$umanager = new UserManagement();
+$suck_it_down = new blackhole_message_class();
+$default_mailer = StudipMail::getDefaultTransporter();
+StudipMail::setDefaultTransporter($suck_it_down);
+$umanager->createNewUser($data));
+StudipMail::setDefaultTransporter($default_mailer);
+```
+
+## Mails erstellen
+Um eine Mail zu erstellen, erzeugt man ein neues `StudipMail` Objekt und füllt es über die diversen `add` und `set` Methoden mit Inhalt. Alle `add` und `set` Methoden liefern immer das aktuelle Objekt zurück, damit man mehrere Aufrufe hintereinandersetzen kann ("fluent interface"). Die wesentlichen Methoden sind:
+
+| Funktion | Beschreibung |
+| ------ | ------ |
+| `setSenderEmail($mail)` | setzt die Mailadresse des Absenders. Die Absendeadresse wird im Konstruktor auf `$MAIL_ENV_FROM` vorbelegt. |
+| `setSenderName($name)` | setzt den Namen des Absenders, wird auf `$MAIL_FROM` vorbelegt |
+| `setReplyToEmail($mail)` | setzt die Mailadresse für das reply-to, wird mit `$MAIL_ABUSE` vorbelegt. |
+| `setSubject($subject)` | setzt den Titel der Mail |
+| `addRecipient($mail, $name = *, $type = 'To')` | fügt einen Empfänger hinzu. Der erste Parameter muss die Mailadresse enthalten, der nächste enthält optional den Namen. Mit dem dritten Parameter kann man einstellen ob es sich um einen Standardempfänger ('To'), einen Kopieempfänger ('Cc') oder einen Blindkopieempfänger ('Bcc') handelt. |
+| `addDataAttachment($data, $name, $type = 'automatic/name', $disposition = 'attachment')` | fügt einen Datenanhang der Mail hinzu. Über den $type Parameter sollte man eine korrekten mime-type mit angeben |
+| `addFileAttachment($file_name, $name = *, $type = 'automatic/name', $disposition = 'attachment')`| fügt einen Dateianhang der Mail hinzu. Über den `$type` Parameter kann man einen korrekten mime-type mit angeben, ansonsten wird versucht auf Basis des Dateinamens zu entscheiden. $file_name muss den kompletten Pfad zur Datei enthalten. |
+|`addStudipAttachment($dokument_id)` | fügt einen Dateianhang der Mail hinzu, es muss nur die Stud.IP Dokumenten ID übergeben werden, die anderen Parameter werden dann direkt aus der Datenbank befüllt. |
+| `setBodyText($body)` | setzt den Textinhalt der Mail |
+| `setBodyHtml($body)` | setzt den HTML Inhalt der Mail. Es wird dann immer eine multipart Mail erzeugt, wenn der Textinhalt fehlt wird dafür ein Hilfstext eingefügt.|
+| `send(email_message_class $transporter = null)` | versendet die Mail, mit dem optionalem Parameter kann das transport Objekt vorgegeben werden. Die Methode liefert true zurück wenn die Mail verschickt werden konnte. |
diff --git a/docs/docs/functions/studip-pdo.md b/docs/docs/functions/studip-pdo.md
new file mode 100644
index 0000000..331cbae
--- /dev/null
+++ b/docs/docs/functions/studip-pdo.md
@@ -0,0 +1,91 @@
+---
+id: studip-pdo
+title: StudipPDO
+sidebar_label: StudipPDO
+---
+
+
+Stud.IP benutzt für die Datenbankzugriffe eigene, von `PDO` und `PDOStatement` abgeleitete Klassen. `DBManager::get()` liefert immer automatisch eine Instanz von `StudipPDO` zurück. Man kann dieses Objekt genauso benutzen wie ein Standard-PDO Objekt, darüberhinaus enthält es aber noch ein paar Erweiterungen, die den Umgang mit der Datenbank erleichtern.
+
+[StudipPDO](https://gitlab.studip.de/studip/studip/-/blob/main/lib/classes/StudipPDO.class.php)
+
+## Automatische Parametererkennung, Zusätzliche Parameter Typen
+
+Die an eine Statement übergebenen Parameter werden anhand des PHP Typs auf einen passenden PDO Parametertypen gemappt. D.h. z.B., wenn ein übergebener Parameter `NULL` in PHP ist, wird er auch in der Abfrage als ein SQL `NULL` ersetzt. Es stehen noch neue Parameter Typen zur Verfügung:
+
+### `StudipPDO::PARAM_ARRAY`
+Der Parametertyp erlaubt die Übergabe eines Arrays, das dann zu einer Wertliste expandiert wird. Zu benutzen mit dem `IN ()` Konstrukt in SQL.
+
+Beispiel:
+```php
+$st = DBManager:get()->prepare("SELECT * FROM seminare WHERE status IN(?)");
+$st->execute([[1,2,3,4]]);
+```
+
+wird ausgeführt als
+```sql
+SELECT * FROM seminare WHERE status IN (1, 2, 3, 4)
+```
+
+### `StudipPDO::PARAM_COLUMN`
+Dieser Parametertyp erlaubt die Übergabe eines Strings, der ohne weitere Behandlung in die SQL Abfrage eingesetzt wird. Damit kann z.B. ein Parameter im `ORDER BY` Teil benutzt werden. Weil das ein Sicherheitsproblem sein kann, wird in jedem Fall jedes nicht-Wort Zeichen aus dem Parameter herausgefiltert.
+
+Beispiel:
+```php
+$st = DBManager:get()->prepare("SELECT * FROM auth_user_md5 WHERE perms IN (:perms) ORDER BY :sorter");
+$st->bindValue(':status', ['tutor','dozent']);
+$st->bindValue(':sorter', 'Nachname', StudipPDO::PARAM_COLUMN);
+```
+
+wird ausgeführt als
+```sql
+SELECT * FROM auth_user_md5 WHERE perms IN ('tutor','dozent') ORDER BY Nachname
+```
+
+## Prepared Statements to go
+
+Da Prepared Statements in der Anwendung etwas umständlich sind, wurde ein Abkürzung für häufig benutzte Varianten eingebaut. Das erspart auch die Eingabe der PDO Konstanten für den fetch-mode. Dazu stehen diese fetch Methoden außerdem direkt im StudipPDO Objekt zur Verfügung stellen, mit der Möglichkeit eine Query und die Parameter direkt mit anzugeben.
+
+Beispiele:
+```php
+<?php
+$db = DBManager::get();
+
+//für DELETE UPDATE etc
+$db->execute("DELETE FROM xxx WHERE id=?", [$id]);
+
+//nur das erste Ergebnis der Abfrage als assoc array holen
+$db->fetchOne("SELECT * FROM xxx WHERE id=?", [$id]);
+
+//nur den Wert der ersten Spalte des ersten Ergebnisses der Abfrage holen
+$db->fetchColumn("SELECT id FROM xxx WHERE id=?", [$id]);
+
+//alles als array holen, erste Spalte als Schlüssel, zweite als Wert
+$db->fetchPairs("SELECT id,value FROM xxx WHERE id IN (?)", [$ids]);
+
+//alles als array holen, nur die Werte der ersten Spalte
+$db->fetchFirst("SELECT value FROM xxx WHERE id IN (?)", [$ids]);
+
+//alles als assoc array holen,
+$db->fetchAll("SELECT * FROM xxx WHERE id IN (?)", [$ids]);
+
+//alles als assoc array holen, die erste Spalte wird zum Schlüssel,
+//der Rest wird gruppiert, wenn zu einem Schlüssel mehrere Zeilen vorhanden sind,
+// wird nur die erste zurückgegeben
+$db->fetchGrouped("SELECT id, xxx.* FROM xxx WHERE id IN (?)", [$ids]);
+
+//alles als assoc array holen, die erste Spalte wird zum Schlüssel, die zweite wird
+//gruppiert und als array zurückgegeben
+//(Anm. Arne:) ersetzt: fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_GROUP)
+$db->fetchGroupedPairs("SELECT id, value FROM xxx WHERE id IN (?)", [$ids]);
+
+//dritter Parameter kann bei allen Methoden die Arrays liefern ein callable sein
+//z.B. um bei den Gruppierungen eine Aggregatfunktion zu realisieren
+$count = function ($a) {
+ return count($a);
+};
+$db->fetchGroupedPairs("SELECT id, value FROM xxx WHERE id IN (?)", [$ids], $count);
+
+//das Ergebnis wäre das gleiche wie hier
+$db->fetchPairs("SELECT id, COUNT(*) FROM xxx WHERE id IN (?) GROUP BY id", [$ids]);
+```
diff --git a/docs/docs/functions/user-lookup.md b/docs/docs/functions/user-lookup.md
new file mode 100644
index 0000000..16a0566
--- /dev/null
+++ b/docs/docs/functions/user-lookup.md
@@ -0,0 +1,85 @@
+---
+id: user-lookup
+title: Benutzung der Klasse UserLookup
+sidebar_label: Benutzung der Klasse UserLookup
+---
+
+##
+
+Die neue Klasse (ab Version 2.1) dient zur Filterung von Nutzern nach speziellen Kriterien.
+
+
+### Allgemeines
+Entwickelt wurde diese Klasse, um eine einheitliche, wiederverwendbare Lösung zum Filtern bzw. Suchen von Benutzern anhand bestimmter Kriterien zu schaffen.
+
+Es können verschiedene Kriterien (auch gleichen Typs mit unterschiedlichen Filterwerten) kombiniert werden. Intern wird schlussendlich die Schnittmenge aller einzelnen angewandten Filterkriterien gebildet.
+
+Nach folgende Kritieren kann standardmässig gefiltert werden:
+
+* Studienfach (*fach*)
+* Studienabschluss (*abschluss*)
+* Studienfachsemester (*fachsemester*)
+* Einrichtungszugehörigkeit (*institut*)
+* Nutzerstatus (*status*)
+
+### Methoden der Klasse `UserLookup`
+An dieser Stelle sind die öffentlichen Operation der Klasse `UserLookup` dokumentiert.
+
+#### Klassenmethoden
+* **UserLookup::getValuesForType($type)**
+Liefert alle möglichen Werte für einen gegebenen Typ zurück. Diese Werte werden mit den Standardeinstellungen für eine Stunde im Cache gehalten, um die Datenbank zu entlasten.
+
+* **UserLookup::addType($name, $values_callback, $filter_callback)**
+Fügt ein neues Filterkriterium *type* hinzu. Sollte ein Kriterium mit dem gleichen Namen schon existieren, so wird es überschrieben.
+
+*$values_callback* gibt an, welche Methode genutzt wird, um alle möglichen Werte für dieses Kriterium zurückzuliefern. Diese Methode **sollte** nach Möglichkeit ein eindimensionales, assoziatives Array (Schlüssel = Id des Wertes) zurückgeben.
+
+*$filter_callback* gibt an, welche Methode genutzt wird, um das Filterkriterium anzuwenden. Diese Methode **muss zwingend** eine Liste von Benutzer-Ids zurückgeben, auf die das Filterkriterium zutrifft.
+
+Die beiden Callback-Parameter müssen valide [PHP-Callbacks](http://php.net/manual/language.pseudo-types.php#language.types.callback) sein.
+
+#### Instanzmethoden
+* **setFilter($type, $value)**
+Fügt der momentanen Auswahl an Filtern einen neuen Filter des Kriteriums *$type* hinzu. *$value* kann hierbei entweder ein atomarer Wert oder zur Vereinfachung auch ein Array von Werten sein.
+
+* **clearFilter**
+Löscht alle gesetzten Filterkriterien.
+
+* **execute($flags)**
+Wendet die aktuelle Auswahl an Filtern an. Zurückgegeben wird standardmässig ein Array, das alle passenden Benutzer-Ids enthält.
+Die Rückgabe kann über optionale Flags noch weiter gesteuert werden:
+
+| Flag | Beschreibung |
+| ---- | ---- |
+|FLAG_SORT_NAME|Die zurückgegebenen Ids werden nach Namen der damit verbundenen Benutzern sortiert (aufsteigend nach Nachname und Vorname). |
+|FLAG_RETURN_FULL_INFO|Anstatt eines Array mit den reinen Benutzer-Ids wird ein assoziatives Array zurückgegeben, das die Benutzer-Id als Schlüssel enthält und als Wert ein Array mit den folgenden Angaben des jeweiligen Benutzers: *username*, *Vorname*, *Nachname*, *Email* und *perms* |
+
+
+### Beispiele
+
+Hier sollten einige kleine Bespiele für die Verwendung des `UserLookup` aus dem praktischen Einsatz in Stud.IP gesammelt werden.
+
+```php
+# Create a new UserLookup object
+$user_lookup = new UserLookup;
+
+# Filter all users in their first to sixth fachsemester
+$user_lookup->setFilter('fachsemester', range(1, 6));
+
+# Filter all users with the fach 'fach123' or 'fach456'
+$user_lookup->setFilter('fach', array('fach123', 'fach456'));
+/* Equivalent:
+$user_lookup->setFilter('fach', 'fach123');
+$user_lookup->setFilter('fach', 'fach456');
+*/
+
+# Filter all users that have an 'autor' or 'tutor' permission
+$user_lookup->setFilter('status', array('autor', 'tutor'));
+
+# Get a list of all matching user ids (sorted by the user's names)
+$user_ids = $user_lookup->execute(UserLookup::FLAG_SORT_NAME);
+
+# Get another list of all matching user ids but this time we want
+# the complete unordered dataset
+$user_ids = $user_lookup->execute(UserLookup::FLAG_RETURN_FULL_INFO);
+```
diff --git a/docs/docs/functions/visibility.md b/docs/docs/functions/visibility.md
new file mode 100644
index 0000000..32f9951
--- /dev/null
+++ b/docs/docs/functions/visibility.md
@@ -0,0 +1,194 @@
+---
+id: visibility
+title: VisibilityAPI
+sidebar_label: VisibilityAPI
+---
+
+Mit Hilfe der VisibilityAPI können Sichbarkeitseinstellungen für Benutzer von überall im System (Auch in Plugins) hinzugefügt werden, ohne das direkt in den Sichbarkeitscode eingegriffen werden muss. Die Verwaltung der Einstellungsmöglichkeiten ist ebenfalls ohne Eingriff in bestehenden Code möglich.
+
+## Sichtbarkeitsstufen
+
+Die VisibilityAPI bietet die Möglichkeit, direkt im Dateiensystem festzulegen, welche Sichtbarkeiten zur Verfügung stehen. Der Ordner dafür ist lib/classes/visibility/visibilitySettings.
+Dieser enthält standardmäßig folgende Sichtbarkeitseinstellungen:
+
+* Me
+* Buddies
+* Domain
+* Studip
+* Extern
+
+Beispiel
+```php
+class Visibility_Buddies extends VisibilityAbstract{
+
+ // Soll dieser Status benutzt werden können
+ protected $activated = true;
+
+ // Welche int Repräsentation in der Datenbank
+ protected $int_representation = 2;
+
+ // Was wird in den Einstellungen angezeigt
+ protected $display_name = "Buddies";
+
+ // Was wird bei Visibility::getStateDescription() angezeigt
+ protected $description = "nur für meine Buddies sichtbar";
+
+ // Wann haben zwei Nutzer diesen Status
+ function verify($user_id, $other_id) {
+ return CheckBuddy(get_username($other_id), $user_id) || $user_id == $other_id;
+ }
+}
+```
+Eine Sichtbarkeitseinstellung muss immer die Klasse Visibility_+"Name der Datei" enthalten, die VisibilityAbstract erweitert. Außerdem müssen folgende Attribute und Funktionen vorhanden sein:
+* **activated**: Definiert ob die Sichtbarkeitseinstellung verfügbar sein soll. Änderungen werden erst nach einem Relogg übernommen, da die Sichtbarkeitseinstellungen aus I/O Kostengründen in der Session gespeichert werden.
+* **int_representation**: Legt fest unter welchem Wert eine Sichtbarkeit in der Datenbank gespeichert wird. Dabei darf es keine Überschneidung geben.
+* **display_name**: Beschreibt den Namen unter dem der Punkt in den Einstellungen auftaucht.
+* **description**: Ist eine Beschreibung der Einstellung, die verwendet werden kann, um dem Nutzer die aktuelle Einstellung anzuzeigen
+* **function verify**: Die Hauptaufgabe einer Sichtbarkeitseinstellung ist die verify Funktion. Sie muss immer eine BenutzerID (Gibt den Besitzer des Sichtbarkeitsobjekts an) und eine AndereID (Gibt den Aufrufenden an) erhalten. Der Rückgabe Wert muss `true` sein, wenn der Aufrufer (other_id) in richtiger Relation zum Besitzer (user_id) steht.
+
+Auch eine Sichtbarkeit nobody steht zur Verfügung. Diese ist vor allem für Debugging von Vorteil.
+
+## Sichtbarkeit der Homepage-Elemente
+
+Grundsätzlich kann eine Sichtbarkeitseinstellung durch zwei Möglichkeiten definiert sein:
+* **SichbarkeitsID**: Eine eindeutige ID, die bei der Erstellung generiert wird
+* **Identifier und BenutzerID**: Ein Identifikationsstring kann mit Hilfe einer BenutzerID zu einer SichtbarkeitsID aufgelöst werden. Dies erleichtert zwar die Programmierung kann aber unter Umständen zu Überschneidungen bei Identifikationsstrings führen. Deshalb sollte dabei darauf geachtet werden diesen so eindeutig wie möglich zu wählen.
+
+### Hinzufügen einer Sichtbarkeitseinstellung
+Soll für einen Nutzer eine Sichtbarkeitseinstellung hinzugefügt werden, so muss reicht folgender Aufruf:
+```php
+Visibility::addPrivacySetting($name, $identifier = "", $parent = null, $category = 1, $user = null, $default = null, $pluginid = null)
+```
+Dabei wird eine SichtbarkeitsID erzeugt und zurückgegeben, die gespeichert werden kann.
+* **name**: Der Name unter dem die Sichtbarkeitseinstellung beim Benutzer erscheint
+* **identifier**: Identifikationsstring
+* **parent**: Die Sichtbarkeitseinstellungen bieten an, einen Baum zu erschaffen. Ist das gewünscht muss hier die SichbarkeitsID oder der Identifikationsstring für den Elternknoten angegeben werden. Ansonsten muss hier null angegeben werden, um einen root Knoten zu erstellen. Achtung! Rootknoten sind immer eine Kategorie d.h. es gibt keine Einstellungsmöglichkeiten
+* **category**: Unterscheidet den Typ der Sichtbarkeitseinstellung. 0 steht für eine Kategorie, die keine Einstellungsmöglichkeiten enthält. 1 Für einen "normalen" Einstellungspunkt.
+* **user**: Gibt den Benutzer an, für den die Sichtbarkeit angelegt wird. Ist dieser Wert `null` so wird der aktuell angemeldete Benutzer verwendet.
+* **default**: Kann angeben auf welcher Sichtbarkeit der Wert zu Beginn steht. Ist dieser Wert `null` so wird der Standardwert des `user` verwendet
+* **pluginid**: Kann verwendet werden um der Sichbarkeit zwangsweiße eine PluginID zuzuweisen. Wird die Funktion aus einem Plugin aufgerufen, so ist es nicht nötig eine PluginID anzugeben, da die API diese selbständig herausfindet.
+
+### Ändern einer Sichtbarkeitseinstellung
+Die Funktion `updatePrivacySetting` erhält die selben Parameter, wie `addPrivacySetting` und wird verwendet um eine Sichtbarkeitseinstellung upzudaten. Dabei wird die alte Sichbarkeitseinstellung gelöscht und eine neue erzeugt. Dies erleichtert dem Programmierer die Arbeit um nicht prüfen zu müssen, ob eine Sichtbarkeit bereits existiert. Ist eine Sichtbarkeit an ein Eingabefeld gekoppelt kann die Funktion `updatePrivacySettingWithTest` verwendet werden die Zusätzlich noch als ersten Parameter einen String erhält. Ist dieser String leer so wird die Sichtbarkeitseinstellung nur gelöscht.
+
+### Löschen einer Sichtbarkeitseinstellung
+Die Funktion
+```php
+Visibility::removePrivacySetting($id, $user = null)
+```
+löscht eine Sichtbarkeitseinstellung anhand einer SichbarkeitsID ($id) oder anhand eines Identifikationsstring($id) und einer BenutzerID ($user)
+
+### Bulkfunktionen
+Bei einer Migration kann es notwendig sein, allen Nutzern eine neue Sichbarkeitseinstellung hinzuzufügen. Dafür kann der Befehl
+```php
+Visibility::addPrivacySettingForAll($name, $identifier = "", $parent = null, $category = 1, $default = null, $pluginid = null)
+```
+verwendet werden. Zum löschen aller Einträge eines Identifikationsstrings wird die Funktion
+```php
+Visibility::removeAllPrivacySettingForIdentifier($ident)
+```
+verwendet.
+
+### Überprüfung
+Um dann im Code eine Sichtbarkeit zu überprüfen kann folgender Code verwendet werden:
+```php
+//Überprüfung mit ID
+if (Visibility::verify(1234)) {
+ echo 'Ich darf VisibilityID 1234 sehen';
+}
+
+//Überprüfung mit Identifier und Benutzername
+if (Visibility::verify('homepageelement', $aufgerufener_benutzer->md5) {
+ echo 'Ich darf das homepageelement von '.$aufgerufener_benutzer.' sehen';
+}
+
+//Überprüfung für anderen Nutzer
+if (Visibility::verify('homepageelement', $aufgerufener_benutzer->md5, $test_benutzer->md5) {
+ echo $test_benutzer.' darf das homepageelement von '.$aufgerufener_benutzer.' sehen;
+}
+```
+
+# Alte Version
+
+## Sichtbarkeitsstufen
+Erweiterte Möglichkeiten zur Festlegung der persönlichen Privatsphäre und Sichtbarkeiten stehen ab der Stud.IP-Version 2.0 zur Verfügung.
+
+Die Funktionen zum Abfragen der Sichtbarkeiten sind in lib/user_visible.inc.php definiert. Die vorhandenen Sichtbarkeitsstufen sind dort als Konstanten definiert:
+
+* **VISIBILITY_ME**: Nur für den Nutzer selbst (und dessen evtl. Standardvertretungen mit Homepagebearbeitungsrecht)
+* **VISIBILITY_BUDDIES**: für Buddies aus dem Adressbuch
+* **VISIBILITY_DOMAIN**: für die eigene(n) Nutzerdomäne(n)
+* **VISIBILITY_STUDIP**: für alle in Stud.IP eingeloggten Nutzer
+* **VISIBILITY_EXTERN**: auf externen Seiten
+
+# allgemeine Sichtbarkeit einer Kennung
+Soll die Sichtbarkeit einer Kennung abgefragt werden, so gibt es dafür die Methoden `get_visibility_by_id` bzw. `get_visibility_by_username` bzw. `get_visibility_by_state`.
+
+```php
+// Liefert true oder false, je nach Sichtbarkeit der Kennung
+$visibility = get_visibility_by_username('tester');
+
+/*
+ * Liegt die in der Datenbank hinterlegte Sichtbarkeit
+ * bereits vor, so kann wie folgt abgefragt werden:
+ */
+// Sei die Sichtbarkeit gleich 'yes'
+$db_vis = 'yes'
+
+$visibility = get_visibility_by_state($db_vis, get_userid('tester'));
+```
+Hier kommt als Ergebnis also heraus: "Darf ich die Kennung sehen?", das hängt nicht nur von den Sichtbarkeitsinstellungen der Kennung ab, sondern auch von meinen eigenen Rechten (Root sieht alles).
+
+Um explizit die globale Sichtbarkeit, unabhängig von Root-Rechten o.ä. abfragen zu können, existieren die Methoden `get_global_visibility_by_id` und `get_global_visibililty_by_username`, die als Parameter die User-ID bzw. den Usernamen erhalten und die in der Datenbank hinterlegte Sichtbarkeit zurückgeben. Hier kommt also ein Wert aus der Menge `{'yes', 'no', 'always', 'never', 'unknown', 'global'}` heraus
+
+Zur Abfrage der Sichtbarkeit in einem bestimmten Bereich von Stud.IP gibt es die Methoden `get_local_visibility_by_id` bzw. `get_local_visibility_by_username`. Hiermit kann durch Angabe der User-ID bzw. des Usernamens und des gewünschten Bereichs die Sichtbarkeit in diesem Bereich abgefragt werden. Gültige Bereiche sind
+
+* **online** für die „Wer ist online“-Liste
+* **chat** für die Sichtbarkeit des eigenen Chatraums
+* **search** für die Auffindbarkeit in der Personensuche
+* **email** für die Anzeige der Emailadresse
+* **homepage** für die Sichtbarkeitseinstellungen der einzelnen Elemente der Profilseite
+
+Will man z.B. wissen, ob der User mit dem Usernamen 'tester' über die Personensuche auffindbar ist, so kann dies so abgefragt werden:
+
+```php
+$search_visibility = get_local_visibility_by_username('tester', 'search');
+```
+Besonders auf externen Seiten ist es noch nützlich, auch zu wissen, welche Berechtigung der abzufragende User im System hat. Daher kann optional auch angegeben werden, dass diese Berechtigung mit zurückgegeben werden soll:
+
+```php
+$search_visibility = get_local_visibility_by_username('tester', 'search', true);
+```
+führt dann zur Ausgabe
+
+```php
+$search_visibility = Array(
+ 'perms' => 'dozent',
+ 'search' => true
+);
+```
+
+## Sichtbarkeit der Homepage-Elemente
+Auf der Profilseite einer Person werden am Anfang standardmäßig alle Sichtbarkeiten der einzelnen Elemente geladen. Damit wird die Anzahl der Datenbankanfragen minimiert, indem nur eine globale Anfrage für alle Elemente statt eines Queries pro Element ausgeführt werden muss.
+
+Mittels der Funktionen `is_element_visibible_for_user` und `is_element_visible_externally` kann dann überprüft werden, ob ein einzelnes Element anhand seiner Sichtbarkeitseinstellungen für den aktuellen Nutzer angezeigt werden soll.
+
+Hierzu ein Beispiel: Aus der Datenbank wurde geladen, dass das Element „private_phone“ (also die private Telefonnummer) die Sichtbarkeit 1 (=VISIBILITY_ME) hat, also nur für den Besitzer der Homepage selbst angezeigt werden soll. Die Methode `is_element_visible_for_user` bekommt nun als Parameter die ID des aktuellen Users, die ID des Users, zu dem die gerade besuchte Homepage gehört und den Wert der Sichtbarkeit, also 1. Daraus wird nun ermittelt, ob die Telefonnummer angezeigt werden soll oder nicht.
+
+Im Code sieht das so aus:
+
+```php
+// Der "Besitzer" der Homepage hat die ID '12345'
+$visibilities = get_local_visibility_by_id('12345', 'homepage');
+// Der Besucher der Homepage hat die ID 'abcde'
+$private_phone = is_element_visible_for_user('abcde', '12345', $visibilities['private_phone']);
+```
+Geht es nur um einzelne Elemente der Homepage, so kann man auch explizit deren Sichtbarkeit abfragen:
+
+```php
+// Wieder Homepagebesitzer-ID '12345'
+$private_phone_visibility = get_homepage_element_visibility('12345', 'private_phone');
+```
+Aus Performancegründen wird für eine gesamte Homepage nur die erste Variante ausgeführt, wo alle Sichtbarkeiten auf einmal geladen werden.
+
+Über die Methode `get_visible_email` kann die nach außen sichtbare Emailadresse ermittelt werden. Hat ein Nutzer eingestellt, dass die eigene Emailadresse nicht angezeigt werden soll, so wird stattdessen versucht, über die Einrichtungszuordnung dieser Kennung eine Emailadresse zu ermitteln (nur Zuordnungen mit mindestens Recht „autor“). Dabei wird zuerst die Emailadresse der ersten gefundenen Einrichtung verwendet, sollte es mehrere Einrichtungszuordnungen geben und eine davon als Standardeinrichtung definiert sein, so wird diese Email verwendet. Bei keiner gefundenen Zuordnung wird ein Leerstring als Emailadresse zurückgegeben.
diff --git a/docs/docs/functions/wysiwyg.md b/docs/docs/functions/wysiwyg.md
new file mode 100644
index 0000000..df64ad2
--- /dev/null
+++ b/docs/docs/functions/wysiwyg.md
@@ -0,0 +1,100 @@
+---
+id: wysiwyg
+title: Wysiwyg-Editor
+sidebar_label: Wysiwyg-Editor
+---
+
+## Der Wysiwyg-Editor in Stud.IP
+
+### Allgemeines
+
+%rfloat margin-left=1ex% Attach:wysiwyg-demo.png
+In Stud.IP gibt es auch einen Wysiwyg-Editor, der zur Eingabe von formatierten Inhalten (Text, Bilder, Links, Tabellen usw.) verwendet werden kann. Dieser Editor ersetzt HTML-Textfelder durch eine grafische Oberfläche, die gängigen Textverarbeitungssystemen ähnelt (z.B. [LibreOffice](http://www.libreoffice.org/)). Das Akronym WYSIWYG steht für "*What You See Is What You Get*", auf Deutsch: "*Was Du siehst, ist das, was Du bekommst*". Die von uns verwendete Komponente ist der [**ckeditor**](http://ckeditor.com) und kann als Alternative zur Eingabe von Stud.IP-Formatierung verwendet werden. Inhalte im System sind entweder klassische Stud.IP-Formatierung oder HTML-Inhalte aus dem Wysiwyg-Editor. Es gibt keine Vermischung: Stud.IP-Formatierung wird in mit dem Editor erstellten Inhalten nicht ausgewertet (mit Ausnahme spezieller Markup-Plugins) und HTML-Inhalte werden in Stud.IP-Formatierung nicht ausgewertet.
+
+### Konfiguration
+
+%rfloat margin-left=1ex% Attach:wysiwyg-konfig.png
+Der Editor kann systemweit über die Einstellung `WYSIWYG` in der globalen Konfiguration eingeschaltet werden. Dabei ist zu beachten, daß mit dem Editor erstellte Inhalte als HTML in der Datenbank landen, die beim Ausschalten des Editors zurückbleiben. Wenn der Editor systemweit eingeschaltet ist, wird er standardmäßig für alle Nutzer angeboten - diese können ihn aber für sich individuell wieder deaktivieren. Es gibt also aus Entwicklersicht keine Garantie, daß bei systemweit aktiviertem Editor auch tatsächlich alle neu erstellten Inhalte in HTML vorliegen. Daher ist die PHP-API so konstruiert, daß sie mit den verschiedenen Konstellationen weitgehend transparent umgehen kann.
+
+### Verwendung
+
+Wenn ein Eingabefeld den Editor verwenden soll, ist dieses mit der CSS-Klasse "`wysiwyg`" auszuzeichnen (ggf. zusätzlich zu "`add_toolbar`" für die klassische Formatierungs-Toolbar). Der Inhalt muß für das *textarea*-Element mit der Funktion `wysiwygReady()` aufbereitet werden, die analog zu `htmlReady()` funktioniert, aber ggf. Stud.IP-Formatierung vor dem Bearbeiten in HTML übersetzt.
+
+Beispiel:
+```php
+<textarea class="add_toolbar wysiwyg" name="content">
+ <?= wysiwygReady($content) ?>
+</textarea>
+```
+
+Der ensprechende Code im Controller, der die Eingabe entgegennimmt und weiterverarbeitet, sollte die Nutzereingabe durch den *HTMLPurifier* laufen lassen. Dazu gibt es die Funktion `Studip\Markup::purifyHtml()`, die eine entsprechende Filterung vornimmt, sofern die Eingabe tatsächlich HTML ist:
+
+Beispiel:
+```php
+$content = Studip\Markup::purifyHtml(Request::get('content'));
+```
+
+Die Verwendung des Editors für einzeilige Eingabefelder (d.h. `<input>`) wird derzeit allerdings nicht unterstützt.
+
+#### Weitere Funktionen der Klasse `Studip\Markup`
+
+In den allermeisten Fällen sollte die oben beschriebene API ausreichend sein. Für spezielle Einsatzfälle gibt es aber noch weitere Funktionen in der `Markup`-Klasse, die hier kurz beschrieben sind:
+
+* `Studip\Markup::editorEnabled()`\\
+ Diese Funktion liefert `true` zurück, wenn der Editor systemweit und auf Nutzerebene aktiviert ist.
+
+* `Studip\Markup::isHtml($text)`\\
+ Diese Funktion liefert `true` zurück, wenn der übergebene Inhalt von Stud.IP als HTML interpretiert wird.
+
+* `Studip\Markup::markAsHtml($text)`\\
+ Markiert einen Inhalt als HTML. Ist der Inhalt bereits entsprechend markiert, wird er nicht verändert.
+
+* `Studip\Markup::purifyHtml($html)`\\
+ Falls der Inhalt als HTML markiert ist, wird er mit dem *HTMLPurifier* gefiltert. Andere Inhalte werden nicht verändert.
+
+* `Studip\Markup::markupToHtml($text, $trim = true, $mark = true)`\\
+ Konvertiert Inhalte aus Stud.IP-Formatierung in HTML, damit diese z.B. im Editor bearbeitet werden können. War der Inhalt bereits HTML, wird er nur durch den *HTMLPurifier* gefiltert. Normalerweise wird das Resultat auch gleich als HTML markiert, dieses Verhalten kann aber abgeschaltet werden.
+
+* `Studip\Markup::removeHtml($html)`\\
+ Entfernt alle HTML-Elemente aus dem Inhalt, z.B. um diesen anschließend wieder ohne Wysiwyg-Editor bearbeiten zu können. Ist der Inhalt kein HTML, wird er nicht verändert.
+
+Beispiel 1: Vordefinierte Inhalte als über den Editor bearbeitbares HTML generieren, z.B. als Vorbelegung für ein Eingabefeld mit Wysiwyg-Editor:
+
+```php
+$html = Studip\Markup::markAsHtml('<h1>' . htmlReady($title) . '</h1>');
+```
+
+Beispiel 2: Zusammenfügen von formatierten Inhalten, so daß das Resultat je nach Nutzereinstellung des Editors Text oder HTML ist (ein Beispiel dafür ist das Zusammenfügen einer in Editor erstellen Nachricht mit der Signatur des Nutzers):
+
+```php
+if (Studip\Markup::editorEnabled()) {
+ $result = Studip\Markup::markupToHtml($part1) . Studip\Markup::markupToHtml($part2);
+} else {
+ $result = Studip\Markup::removeHtml($part1) . Studip\Markup::removeHtml($part2);
+}
+```
+
+#### Javascript-API
+
+Neben der regulären Javascript-API des ckeditor, die auch in Stud.IP verwendet werden kann (natürlich nur, sofern der Editor aktiviert ist), gibt es noch eine kleine Anzahl von Einstellungen und Hilfsfunktionen in Stud.IP:
+
+* `STUDIP.wysiwyg_enabled`\\
+ Diese Property ist `true`, wenn der Editor systemweit aktiviert ist.
+
+* `STUDIP.editor_enabled`\\
+ Diese Property ist `true`, wenn der Editor im aktuellen Kontext aktiv ist - d.h. er ist systemweit aktiviert, der Nutzer hat ihn nicht ausgeschaltet und der Editor funktioniert auf dem Client (oder glaubt zumindest, daß er es tut).
+
+* `STUDIP.wysiwyg.isHtml(text)`\\
+ Diese Funktion liefert `true` zurück, wenn der übergebene Inhalt von Stud.IP als HTML interpretiert wird.
+
+* `STUDIP.wysiwyg.markAsHtml(text)`\\
+ Markiert einen Inhalt als HTML. Ist der Inhalt bereits entsprechend markiert, wird er nicht verändert.
+
+Beispiel:
+
+```javascript
+posting = jQuery('textarea[name=content]').val();
+if (STUDIP.editor_enabled) {
+ posting = STUDIP.wysiwyg.markAsHtml(posting);
+}
+```
diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md
new file mode 100644
index 0000000..43df86a
--- /dev/null
+++ b/docs/docs/getting-started.md
@@ -0,0 +1,28 @@
+---
+title: Getting Started
+slug: /
+---
+
+## Step 1: Generate a new Docusaurus site
+
+If you haven't already, generate a new Docusaurus site using the classic template:
+
+```shell
+npx @docusaurus/init@latest init my-website classic
+```
+
+## Step 2: Start your Docusaurus site
+
+Run the development server in the newly created `my-website` folder:
+
+```shell
+cd my-website
+
+npx docusaurus start
+```
+
+Open `docs/getting-started.md` and edit some lines. The site reloads automatically and display your changes.
+
+## That's it!
+
+Congratulations! You've successfully run and modified your Docusaurus project.
diff --git a/docs/docs/jsonapi/activitystreams.md b/docs/docs/jsonapi/activitystreams.md
new file mode 100644
index 0000000..0dbd05a
--- /dev/null
+++ b/docs/docs/jsonapi/activitystreams.md
@@ -0,0 +1,140 @@
+---
+title: Activity Streams
+---
+
+Mit Stud.IP Version 3.5 wurde eine neue API zum Erzeugen, Darstellen
+und Filtern von kontextrelevanten Aktivitäten eingeführt. Diese API
+kann u.a. dafür genutzt werden um Nutzern einen schnellen Überblick
+über die für ihn relevanten Information/Aktivitäten zu geben.
+
+## Schema "activities"
+
+Activity Streams enthalten Objekte des Typs "activities". Diese geben
+enthalten eine textuelle Beschreibung der Aktivität, ein Datum, eine
+(etwas) detailiertere Beschreibung und die Art der Aktivität (das
+Verb). Aktivitäten beziehen sich auf einen Akteur (in der Regel ein
+Nutzer), einen Kontext, in dem sie stattfinden, und ein Objekt, auf
+das sie sich beziehen.
+
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+title | knappe Beschreibung der Aktivität: "Wer tut was mit wem/was wo?"
+mkdate | Datum der Aktivität
+content | etwas detailiertere Beschreibung der Aktivität
+verb | Art der Aktivität
+activity-type | Typ der Aktivität
+
+Die verwendeten Verben sind normiert. Der Wertebereich umfasst:
+
+<code>answered, attempted, attended, completed, created, deleted,
+edited, experienced, failed, imported, interacted, passed, shared,
+sent, voided</code>
+
+### Relationen
+
+Die Relationen sind nicht änderbar und können nur ausgelesen werden.
+
+ Relation | Beschreibung
+-------- | ------------
+actor | Wenn der Akteur der Aktivität ein Nutzer ist, wird mit dieser Relation auf ihn verwiesen.
+context | der Kontext, in dem die Aktivität stattfindet; kann eine der folgenden sein: Veranstaltung, Einrichtung, Nutzer oder System.
+object | das Objekt, mit dem die Aktivität stattfindet; falls möglich wird hier auf eine Route in der JSON:API verwiesen
+
+## Alle Aktivitäten auslesen
+
+```shell
+curl --request GET \
+ --url https://example.com/jsonapi.php/v1/users/<USER-ID>/activitystream \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+```javascript
+fetch('https://example.com/jsonapi.php/v1/users/<USER-ID>/activitystream', {
+ method: 'GET',
+ mode: 'cors',
+ headers: new Headers({
+ 'Authorization': `Basic ${btoa('test_autor:testing')}`
+ })
+}).then(response => console.log(response))
+```
+
+
+Mit dieser Route können die Aktivitäten ausgelesen werden, die für einen
+Nutzer sichtbar sind. Der Activity Stream wird paginiert ausgegeben.
+Standardmäßig werden nur Aktivitäten **der letzten 6 Monate**
+ausgegeben. Diese Einschränkung kann mit Hilfe des URL-Parameters
+'filter' verändert werden.
+
+### HTTP Request
+
+`GET /users/{id}/activitystream`
+
+### URL-Parameter
+
+Parameter | Beschreibung
+--------- | -------
+filter | Filtermöglichkeit der anzuzeigenden Aktivitäten (Zeit und Typ)
+include | ermöglicht das Inkludieren des Akteurs, des Kontexts und des Objekts in die JSON:API-Antwort
+page | Einstellmöglichkeiten [zur Paginierung](#paginierung)
+
+#### URL-Parameter 'filter'
+
+```shell
+curl --request GET \
+ --url 'https://example.com/jsonapi.php/v1/users/<USER-ID>/activitystream?filter\[start\]=1263078000&filter\[end\]=1409695200&filter[activity-type]=documents' \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit diesem URL-Parameter kann nach Typ und Datum der Aktivitäten
+gefiltert werden. Möglich sind folgende Filter:
+
+Filter | Beschreibung
+--------------------- | ------------
+filter[start] | zeitliche Beschränkung: Start des Abfrageintervalls
+filter[end] | zeitliche Beschränkung: Ende des Abfrageintervalls
+filter[activity-type] | nur Aktivitäten dieses Typs/dieser Typen werden zurückgeliefert
+
+Mit Hilfe der Parameter 'start' und 'end' kann das Abfrageintervall
+verändert werden. Standardmäßig werden alle Aktivitäten der letzten 6
+Monate bis zum aktuellen Zeitpunkt zurückgeliefert. Mit 'start' und
+'end' können diese Intervallgrenzen beliebig gestaltet werden. Für diese
+beiden Parameter können nur ganzzahlige Werte angegeben werden, die
+die Anzahl der Sekunden seit dem 01.01.1970 bis zum gewünschten
+Zeitpunkt angeben ('unix epoch time').
+
+Der Parameter 'activity-type' schränkt die Aktivitäten nach Typ ein.
+Mögliche Werte sind:
+
+`activity`, `documents`, `forum`, `literature`, `message`, `news`, `participants`, `schedule`, `wiki`
+
+Um nach mehreren Aktivitätstypen zu filtern, können mehrere dieser
+Typen durch Komma getrennt verwendet werden.
+
+#### URL-Parameter 'include'
+
+```shell
+curl --request GET \
+ --url 'https://example.com/jsonapi.php/v1/users/<USER-ID>/activitystream?include=actor,context'\
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Werte | Beschreibung
+---------- | ------------
+actor | inkludiert die Akteure der gelieferten Aktivitäten
+context | inkludiert die Kontexte der gelieferten Aktivitäten
+object | inkludiert die Objekte der gelieferten Aktivitäten
+
+Der 'include'-Parameter wird der JSON:API-Spezifikation entsprechend
+verwendet. Es können auch mehrere Werte durch Komma getrennt angegeben werden.
+
+### Meta-Informationen
+
+Damit klar ersichtlich ist, welche Filter für die Abfrage galten,
+werden diese Informationen als Top-Level-'meta'-Objekt zurückgegeben.
+
+### Authorisierung
+
+Mit dieser Route kann nur der Nutzer selbst oder Root-Nutzer
+diejenigen Aktivitäten sehen, die für einen Nutzers sichtbar wären.
diff --git a/docs/docs/jsonapi/blubber.md b/docs/docs/jsonapi/blubber.md
new file mode 100644
index 0000000..6f0035d
--- /dev/null
+++ b/docs/docs/jsonapi/blubber.md
@@ -0,0 +1,421 @@
+---
+title: Blubber
+---
+
+:::info
+Blubber ermöglicht innerhalb von Veranstaltungen mit anderen Stud.IP-Teilnehmern zu chatten.
+Wir unterscheiden in öffentliche-, private- und veranstaltungsbezogene Blubber.
+:::
+
+## Schema 'blubber-postings'
+Der Inhalt wird als plain-text und html gespeichert. Meta-Daten geben Informationen über den Zeitpunkt und
+das Thema einer Nachricht.
+
+### Attribute
+
+Attribut | Beschreibung
+--------------- | ------------
+context-type | die Art des Kontexts; Veranstaltung ("course"), Öffentlich ("global") oder Nutzer ("user")
+content | der Text des Blubber-Beitrags; kann Stud.IP-Markup enthalten
+content-html | der Text des Blubber-Beitrags; als HTML formatiert
+mkdate | Anlegedatum
+chdate | Datum der letzten Änderung
+discussion-time | Datum der letzten Aktivität
+tags | eine Liste von Tags
+
+### Relationen
+
+ Relation | Beschreibung
+--------- | ------------
+author | Verfasser der Nachricht
+comments | Untergeordnete Blubber
+context | Wem wird der Blubber angezeigt: users, courses, public
+mentions | Thema eines Blubber-Eintrags
+parent | Übergeordneter Blubber-Eintrag
+resharers |
+
+
+## Alle Beiträge
+
+```shell
+curl --request GET \
+ --url https://example.com/blubber-postings \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+```javascript
+fetch('https://example.com/blubber-postings', {
+ method: 'GET',
+ mode: 'cors',
+ headers: new Headers({
+ 'Authorization': `Basic ${btoa('test_autor:testing')}`
+ })
+}).then(response => console.log(response))
+```
+
+Es werden alle Blubber-Beiträge, die man im Stud.IP sehen könnte, angezeigt.
+
+### HTTP Request
+
+`GET /blubber-postings`
+
+### URL-Parameter
+
+Parameter | Beschreibung
+--------- | -------
+filter | Filtermöglichkeit der anzuzeigenden Blubber-Beiträge
+include | abhängige Ressourcen, die auch zurückgeliefert werden ([JSON:API-Spezifikation](http://jsonapi.org/format/#fetching-includes))
+page | Einstellmöglichkeiten [zur Paginierung](#paginierung)
+
+#### URL-Parameter 'filter'
+
+Mit diesem URL-Parameter kann nach Typ und Datum der Aktivitäten
+gefiltert werden. Möglich sind folgende Filter:
+
+Beispiel-Url: "https://example.com/blubber-postings?filter[user]=205f3efb7997a0fc9755da2b535038da"
+
+Filter | Beschreibung
+--------------- | ------------
+filter[course] | Filtert Blubber-Einträge für eine Veranstaltung
+filter[user] | Filter Blubber-Einträge für einen Nutzer
+
+#### URL-Parameter 'include'
+
+Fügt folgende Attribute in die Ausgabe hinzu.
+
+Wert | Beschreibung
+--------- | ------------
+author | Den Verfasser eines Blubbers
+comments | Angehangene Blubber
+context | Wem wird der post angezeigt (users, courses, public)
+mentions |
+resharers |
+
+
+## Beitrag auslesen
+Einen gezielten Blubber-Eintrag auslesen.
+### HTTP Request
+
+`GET /blubber-postings/{id}`
+
+### Parameter
+
+Parameter | Beschreibung
+--------- | -------
+id | ID des Blubber-Posts
+
+### Authorisierung
+
+Diese Route kann von allen Nutzern verwendet werden.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/blubber-postings/<posting-id> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+## Beitrag anlegen
+
+ ```shell
+ curl --request POST \
+ --url https://example.com/blubber-postings \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data \
+ '{"data":{"type":"blubber-postings","attributes":{"context-type":"course","content":"Ein neuer blubberpost"},"relationships":{"context":{"data":{"type":"courses","id":"<CID>"}}}}}'
+ ```
+
+Mit dieser Route kann ein Blubber-Beitrag angelegt werden. Dies kann
+ein öffentlicher oder privater Beitrag sein, aber auch Blubber in
+Veranstaltungen können darüber angelegt werden.
+
+### HTTP Request
+
+ `POST /blubber-postings`
+
+### HTTP Request Body
+
+Im Request-Body muss der neue Beitrag als ``resource object`` vom Typ
+"blubber-postings" sein.
+
+Notwendig sind die Attribute "content" und "context-type".
+
+Abhängig vom Wert des Attributs "context-type", muss außerdem eine
+"context"-Relation angegeben werden.
+
+Hat dieses Attribut den Wert "course", muss als "context"-Relation
+eine Veranstaltung als ``resource identifiert`` angegeben werden.
+
+### Parameter
+
+ Bei diesem Request sind keine Parameter notwendig.
+
+### Authorisierung
+
+Diese Route kann von allen Nutzern verwendet werden.
+
+
+## Beitrag editieren
+
+ Aktualisiert einen Blubber-Beitrag.
+
+### HTTP Request
+
+ `PATCH /blubber-postings/{id}`
+
+### Parameter
+
+ Parameter | Beschreibung
+ --------- | -------
+ id | ID des Blubber-Posts
+
+### Authorisierung
+
+Der Sender des Requests muss Besitzer des Blubber-Beitrags oder Root sein.
+
+ ```shell
+ curl --request PATCH \
+ --url https://example.com/blubber-postings/<blubber-id> \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "root@studip:testing" | base64`" \
+ --data
+ '{"data":{"type":"blubber-postings","attributes":{"context-type":"course","content":"Ein veränderter blubberpost"}, "relationships":{"context":{"data":{"type":"courses","id":"a07535cf2f8a72df33c12ddfa4b53dde"}}}}}'
+ ```
+
+## Beitrag löschen
+
+ Löscht einen Blubber-Eintrag.
+
+### HTTP Request
+
+ `DELETE /blubber-postings/{id}`
+
+### Parameter
+
+ Parameter | Beschreibung
+ --------- | -------
+ id | ID des Blubber-Posts
+
+### Authorisierung
+
+Der Sender des Requests muss Besitzer des Blubber-Beitrags oder Root sein.
+
+ ```shell
+ curl --request DELETE \
+ --url https://example.com/blubber-postings/<blubber-id> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ ```
+
+## Relation 'author'
+
+Gibt den Author eines Blubber-Posts zurück.
+
+### HTTP Request
+
+ `GET /blubber-postings/{id}/relationships/author`
+
+### Parameter
+
+ Parameter | Beschreibung
+ --------- | -------
+ id | ID des Blubber-Posts
+
+
+### Authorisierung
+
+Diese Route kann von allen Nutzern verwendet werden.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/blubber-postings/<posting-id>/relationships/author \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+
+## Kommentare eines Blubber-Beitrags
+
+Gibt alle Kommentare eines Blubber-Beitrags zurück.
+
+### HTTP Request
+
+ `GET /blubber-postings/{id}/comments`
+
+### Parameter
+
+ Parameter | Beschreibung
+ --------- | -------
+ id | ID des Blubber-Posts
+
+### Authorisierung
+
+Diese Route kann von allen Nutzern verwendet werden.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/blubber-postings/<posting-id>/comments \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+## Beitrag kommentieren
+
+Erstellt einen Kommentar zu einem Blubber-Beitrag.
+
+### HTTP Request
+
+ `POST /blubber-postings/{id}/comments`
+
+### Parameter
+
+ Parameter | Beschreibung
+ --------- | -------
+ id | ID des Blubber-Posts
+
+### Authorisierung
+
+Diese Route kann von allen Nutzern verwendet werden.
+
+ ```shell
+ curl --request POST \
+ --url https://example.com/blubber-postings/<posting-id>/comments \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "root@studip:testing" | base64`" \
+ --data
+ '{"data": {"type": "blubber-postings","attributes": {"content": "Ein neuer blubberkommentar"}}}'
+ ```
+
+## Relation 'comments'
+
+### HTTP Request
+ `GET /blubber-postings/{id}/relationships/comments`
+
+### Parameter
+
+ Parameter | Beschreibung
+ --------- | -------
+ id | ID des Blubber-Posts
+
+### Authorisierung
+
+Diese Route kann von allen Nutzern verwendet werden.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/blubber-postings/<posting-id>/relationships/comments \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+## Relation 'context'
+
+Gibt den Scope (Sichtbarkeit) eines Blubber-Beitrags zurück.
+
+### HTTP Request
+
+ `GET /blubber-postings/{id}/relationships/context`
+
+### Parameter
+
+ Parameter | Beschreibung
+ --------- | -------
+ id | ID des Blubber-Posts
+
+### Authorisierung
+
+Diese Route kann von allen Nutzern verwendet werden.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/blubber-postings/<posting-id>/relationships/context \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+## Erwähnungen eines Beitrags
+Gibt an, ob und in welchen Beiträgen eine Referenz zu diesem Beitrag gibt.
+
+### HTTP Request
+
+ `GET /blubber-postings/{id}/mentions`
+
+### Parameter
+
+ Parameter | Beschreibung
+ --------- | -------
+ id | ID des Blubber-Posts
+
+### Authorisierung
+
+Diese Route kann von allen Nutzern verwendet werden.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/blubber-postings/<posting-id>/mentions \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+## Relation 'mentions'
+Gibt die Referenz der Beiträge zurück, in denen dieser Beitrag erwähnt wird.
+
+### HTTP Request
+
+ `GET /blubber-postings/{id}/relationships/mentions`
+
+### Parameter
+
+ Parameter | Beschreibung
+ --------- | -------
+ id | ID des Blubber-Posts
+
+### Authorisierung
+
+Diese Route kann von allen Nutzern verwendet werden.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/blubber-postings/<posting-id>/relationships/mentions \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+## Relation 'resharers'
+
+Gibt die Referenz von Usern zurück, die diesen Beitrag geteilt haben.
+
+### HTTP Request
+
+ `GET /blubber-postings/{id}/relationships/resharers`
+
+### Parameter
+
+ Parameter | Beschreibung
+ --------- | -------
+ id | ID des Blubber-Posts
+
+### Authorisierung
+
+Diese Route kann von allen Nutzern verwendet werden.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/blubber-postings/<posting-id>/relationships/resharers \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+## Blubber-Stream auslesen
+
+Gibt eine Folge von Blubber-Einträgen zurück.
+
+ `GET /blubber-streams/{id}`
+
+### Parameter
+
+ Parameter | Beschreibung
+ --------- | -------
+ id | ID des Blubber-Streams
+
+### Authorisierung
+
+Diese Route kann von allen Nutzern verwendet werden.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/blubber-postings/<posting-id>/blubber-streams/<stream-id> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
diff --git a/docs/docs/jsonapi/contacts.md b/docs/docs/jsonapi/contacts.md
new file mode 100644
index 0000000..a9bd42f
--- /dev/null
+++ b/docs/docs/jsonapi/contacts.md
@@ -0,0 +1,167 @@
+---
+title: Kontakte
+---
+
+
+Nutzer können in Stud.IP sich andere Nutzer als Kontakte merken. Dafür
+ist kein neuer Ressourcentyp nötig.
+
+## Alle Kontakte
+
+```shell
+curl --request GET \
+ --url https://example.com/users/<ID>/contacts \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route können alle Kontakte eines Nutzers ausgelesen werden.
+
+### HTTP Request
+
+`GET /users/{id}/contacts`
+
+Parameter | Beschreibung
+--------- | ------------
+id | die ID des Nutzers
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Jeder Nutzer kann seine eigenen Kontakte sehen.
+
+
+## Alle Kontakt-IDs eines Nutzer
+
+```shell
+curl --request GET \
+ --url https://example.com/users/<ID>/relationships/contacts \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route können alle IDs der Kontakte eines Nutzers ausgelesen werden.
+
+(siehe http://jsonapi.org/format/#fetching-relationships)
+
+### HTTP Request
+
+`GET /users/{id}/relationships/contacts`
+
+Parameter | Beschreibung
+--------- | ------------
+id | die ID des Nutzers
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Jeder Nutzer kann seine eigenen Kontakte sehen.
+
+
+## Kontakte eines Nutzers setzen
+
+```shell
+curl --request PATCH \
+ --url https://example.com/users/<ID>/relationships/contacts \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --header "Content-Type: application/vnd.api+json" \
+ --data '{"data": [ \
+ {"type": "users","id":"<id1>"}, \
+ {"type": "users","id":"<id2>"}, \
+ {"type": "users","id":"<id3>"} \
+ ]}'
+```
+
+Mit dieser Route kann man die alle Kontakte eines Nutzers setzen.
+
+(siehe http://jsonapi.org/format/#crud-updating-to-many-relationships)
+
+
+### HTTP Request
+
+`PATCH /users/{id}/relationships/contacts`
+
+Parameter | Beschreibung
+--------- | ------------
+id | die ID des Nutzers
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Jeder Nutzer kann seine eigenen Kontakte setzen.
+
+
+## Kontakte eines Nutzers hinzufügen
+
+```shell
+curl --request POST \
+ --url https://example.com/users/<ID>/relationships/contacts \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --header "Content-Type: application/vnd.api+json" \
+ --data '{"data": [ \
+ {"type": "users","id":"<id4>"} \
+ ]}'
+```
+
+Mit dieser Route kann man Kontakte eines Nutzers hinzufügen.
+
+(siehe http://jsonapi.org/format/#crud-updating-to-many-relationships)
+
+
+### HTTP Request
+
+`POST /users/{id}/relationships/contacts`
+
+Parameter | Beschreibung
+--------- | ------------
+id | die ID des Nutzers
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Jeder Nutzer kann seine eigenen Kontakte setzen.
+
+
+## Kontakte eines Nutzers löschen
+
+```shell
+curl --request DELETE \
+ --url https://example.com/users/<ID>/relationships/contacts \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --header "Content-Type: application/vnd.api+json" \
+ --data '{"data": [ \
+ {"type": "users","id":"<id1>"}, \
+ {"type": "users","id":"<id4>"} \
+ ]}'
+```
+
+Mit dieser Route kann man Kontakte eines Nutzers löschen.
+
+(siehe http://jsonapi.org/format/#crud-updating-to-many-relationships)
+
+
+### HTTP Request
+
+`DELETE /users/{id}/relationships/contacts`
+
+Parameter | Beschreibung
+--------- | ------------
+id | die ID des Nutzers
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Jeder Nutzer kann seine eigenen Kontakte löschen.
diff --git a/docs/docs/jsonapi/courses.md b/docs/docs/jsonapi/courses.md
new file mode 100644
index 0000000..ee231f0
--- /dev/null
+++ b/docs/docs/jsonapi/courses.md
@@ -0,0 +1,258 @@
+---
+title: Veranstaltungen
+---
+
+:::info
+Veranstaltungen sind Gruppen für Seminare, Vorlesungen,
+Übungen etc. Innerhalb von Veranstaltungen werden Materialien geteilt,
+Plugins verwendet, Termine festgelegt uvm. Viele Funktionen von
+Stud.IP sind nur für eine bestimmte Veranstaltung sichtbar.
+:::
+
+## Schema "courses"
+
+### Attribute
+
+Attribut | Beschreibung
+------------- | ------------
+course-number | ID des Kurses
+title | Titel des Kurses
+subtitle | Untertitel des Kurses
+course-type | Art des Kurses (Seminar, Vorlesung...)
+description | Beschreibung des Kurses
+location | Ort der Veranstaltung
+miscellaneous | sonstiges
+
+
+### Relationen
+
+ Relation | Beschreibung
+---------------- | ------------
+institute | Die zugewiesene Institution
+start-semester | Anfangs-Semester der Veranstaltung
+end-semester | End-Semester der Veranstaltung
+files | Referenz auf Files innerhalb der Veranstaltung
+documents | Referenz auf Dokumente innerhalb der Veranstaltung
+document-folders | Ordner für Dateien innerhalb der Veranstaltung
+
+## Schema "course-memberships"
+
+Zeigt die Teilnahme an einer Veranstaltung mit Ihrer Rolle an.
+
+### Attribute
+
+Attribut | Beschreibung
+------------- | ------------
+permission | Rolle des Nutzers (Autor, Dozent, etc...)
+position | Anordnung in der Teilnehmer-Liste
+group | Anordnung in der Teilnehmer-Liste
+mkdate | Erstellungsdatum
+label | die "Funktion" des Teilnehmers (s. Weboberfläche)
+notification | Bekomme ich einmal am Tag eine E-Mail-Benachrichtigung über neue Inhalte in dieser Veranstaltung?
+comment | Teilnehmerkommentar für Lehrende
+visible | Sichtbarkeit im Kurs
+
+Das Feld "visible" ist nur für einen selbst bzw. die Lehrenden der
+Veranstaltung zu sehen.
+
+### Relationen
+
+ Relation | Beschreibung
+---------------- | ------------
+course | Die Veranstaltung für die Teilnehmer
+user | Nutzer der Veranstaltung
+
+### URL-Parameter
+
+Parameter | Default | Beschreibung
+--------- | ------- | ------------
+page[offset] | 0 | der Offset (siehe Paginierung)
+page[limit] | 30 | das Limit (siehe Paginierung)
+filter[q] | - | ein Suchbegriff (mind. 3 Zeichen)
+filter[fields] | all | in welchen Feldern gesucht werden soll
+filter[semester] | all | in welchem Semester gesucht werden soll
+
+Der Parameter "filter[fields]" darf folgende Werte annehmen: 'all', 'title_lecturer_number', 'title', 'sub_title', 'lecturer', 'number', 'comment', 'scope'.
+
+## Alle Veranstaltungen
+Mit dieser Route können alle Veranstaltungen ausgelesen werden.
+
+### HTTP Request
+ `GET /courses`
+
+### Parameter
+
+Diese Route benötigt keine Parameter
+
+
+### Autorisierung
+
+Jeder eingeloggte Nutzer kann diese Route verwenden.
+
+### Parameter
+
+Parameter | Beschreibung
+--------- | -------
+id | ID des Kurses
+
+### Autorisierung
+
+Jeder Teilnehmer des Kurses kann diese Route nutzen.
+
+```shell
+curl --request GET \
+ --url https://example.com/courses \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+## Eine Veranstaltung
+
+Gibt eine Veranstaltung wieder.
+
+### HTTP Request
+ `GET /courses/{id}`
+
+### Parameter
+
+Parameter | Beschreibung
+--------- | -------
+id | ID des Kurses
+
+### Autorisierung
+
+Jeder Teilnehmer des Kurses oder Root kann diese Route nutzen.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/courses/<course-id> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+## Alle Veranstaltungen eines Nutzers
+Gibt alle Veranstaltungen eines Nutzers zurück.
+
+### HTTP Request
+
+ `GET /users/{id}/courses`
+
+### Parameter
+
+Parameter | Beschreibung
+--------- | -------
+id | ID des Nutzers
+
+### Autorisierung
+
+Jeder eingeloggte Nutzer kann diese Route nutzen.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/users/<user-id>/courses \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+## Teilnahmen einer Veranstaltung
+
+Gibt alle Kurse mit dem jeweiligen Teilnehmerstatus eines Nutzers zurück.
+
+### HTTP Request
+ `GET /courses/{id}/memberships`
+
+### Parameter
+
+Parameter | Beschreibung
+--------- | -------
+id | ID des Kurses
+
+### URL-Parameter
+
+Parameter | Default | Beschreibung
+--------- | ------- | ------------
+filter[permission] | - | Rolle des Nutzers in der Veranstaltung
+
+### Autorisierung
+
+Nutzer mit mindestens Adminstatus oder Teilnehmer des Kurses können diese Route benutzen.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/courses/<course-id>/memberships \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+## IDs der Teilnahmen
+
+Gibt die Referenzen auf die Teilnehmer eines Kurses zurück.
+
+### HTTP-Request
+ `GET /courses/{id}/relationships/memberships`
+
+### Parameter
+
+Parameter | Beschreibung
+--------- | -------
+id | ID des Kurses
+
+### Autorisierung
+
+Nutzer mit mindestens Adminstatus oder Teilnehmer des Kurses können diese Route benutzen.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/courses/<course-id>/relationships/memberships \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+## Eine Teilnahme auslesen
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/course-memberships/<ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+Gibt eine Teilnahme wieder.
+
+### HTTP Request
+ `GET /course-memberships/{id}`
+
+### Parameter
+
+Parameter | Beschreibung
+--------- | -------
+id | ID der Teilnahme
+
+### Autorisierung
+
+Nur der Teilnehmer selbst kann die Teilnahme auslesen
+
+
+## Eine Teilnahme ändern
+
+```shell
+ curl --request PATCH \
+ --url https://example.com/course-memberships/<ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --header "Content-Type: application/vnd.api+json" \
+ --data '{"data": {
+ "type": "course-memberships",
+ "id": "<ID>",
+ "attributes": {"group":2,"visible":"no"}
+ }}'
+```
+
+Mit dieser Route kann man die Attribute einer Teilnahme an einer
+Veranstaltung ändern.
+
+### HTTP Request
+ `PATCH /course-memberships/{id}`
+
+### Parameter
+
+Parameter | Beschreibung
+--------- | -------
+id | ID der Teilnahme
+
+### Autorisierung
+
+Nur der Teilnehmer selbst kann die Teilnahme ändern.
diff --git a/docs/docs/jsonapi/discovery.md b/docs/docs/jsonapi/discovery.md
new file mode 100644
index 0000000..5e6504b
--- /dev/null
+++ b/docs/docs/jsonapi/discovery.md
@@ -0,0 +1,48 @@
+---
+title: Discovery
+---
+
+
+Auch wenn JSON:APIs von Haus aus einiges mehr "discoverable" als
+herkömmliche REST-APIs sind, schadet es nicht, eine spezielle Route
+anzubieten, um alle verfügbaren Routen anzuzeigen.
+
+## Schemata
+
+
+### Schema "slim-routes"
+
+Ressourcen vom Typ "slim-routes" repräsentieren die aktiven Routen der Stud.IP-JSON:API.
+
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+methods | ein Vektor von HTTP-Verben wie GET, POST, PATCH und DELETE
+pattern | ein URI-Pattern wie "/file-refs/{id}"
+
+### Relationen
+
+keine Relationen vorhanden
+
+
+## Alle Routen anzeigen
+```shell
+curl --request GET \
+ --url https://example.com/discovery \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route erhält man eine Liste aller aktiven Routen der Stud.IP-JSON:API.
+
+### HTTP Request
+
+`GET /discovery`
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Jeder eingeloggte Nutzer darf diese Route aufrufen.
diff --git a/docs/docs/jsonapi/errors.md b/docs/docs/jsonapi/errors.md
new file mode 100644
index 0000000..a57dc5e
--- /dev/null
+++ b/docs/docs/jsonapi/errors.md
@@ -0,0 +1,17 @@
+---
+title: Fehler
+---
+
+
+Die Stud.IP-JSON:API verwendet die Fehler-Codes, die auch in der <a
+href="http://jsonapi.org/format">JSON-API-Spezifikation</a> verwendet
+werden.
+
+
+Fehler Code | Bedeutung
+---------- | -------
+401 | Unauthorized – Sie haben nicht die erforderliche Berechtigung.
+403 | Forbidden – Die gewünschte Operation steht nicht zur Verfügung.
+404 | Not Found – Die gewünschte Ressource oder Relation konnte nicht gefunden werden.
+409 | Conflict – Beim Anlegen oder Ändern von Ressourcen oder Relationen werden Beschränkungen im Stud.IP verletzt. Beispiel: Eine Ressource falschen Typs soll einer Relation hinzugefügt werden.
+500 | Internal Server Error – Es gibt ein Problem auf dem Server. Versuchen Sie es später erneut!
diff --git a/docs/docs/jsonapi/files.md b/docs/docs/jsonapi/files.md
new file mode 100644
index 0000000..d4803ad
--- /dev/null
+++ b/docs/docs/jsonapi/files.md
@@ -0,0 +1,887 @@
+---
+title: Dateibereich
+---
+
+In Stud.IP hat jeder Nutzer, jede Einrichtung und jede Veranstaltung
+einen eigenen Dateibereich. Dateibereiche sind (spezielle) Ordner.
+Ordner können Dateien und Ordner enthalten, in denen sich wiederum
+Dateien und Ordner befinden können.
+
+Es gibt verschiedene Arten von Ordnern, die sich in der Regel darin
+unterscheiden, wer sie sehen kann und wer Lese- und/oder
+Schreibzugriff auf diese Ordner hat.
+
+## Schemata
+
+
+### Schema "file-refs"
+
+Aus Nutzersicht sind Dateien in Stud.IP Ressourcen vom Typ
+"file-refs". Technisch gesehen sind es allerdings Verweise auf die
+Ressourcen vom Typ "files". Letztere sind die tatsächlich auf der
+Festplatte gespeicherten Dateien, die mithilfe der "file-refs" nur
+verlinkt werden.
+
+Vereinfacht gesagt, hantiert man in der Regel immer mit "file-refs".
+
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+name | der Name der Datei
+description | eine optionale Beschreibung der Datei
+mkdate | das Erstellungsdatum der Datei
+chdate | das Datum der letzten Änderung der Metadaten ('name', 'description', …) der Datei
+downloads | Wie häufig wurde diese Datei heruntergeladen?
+filesize | die Größe der Datei in Byte
+storage | TODO
+
+### Relationen
+
+ Relation | Beschreibung
+-------- | ------------
+file | die tatsächliche Datei auf der Festplatte
+owner | der Nutzer, dem diese Datei gehört
+parent | der Ordner, im dem diese Datei liegt
+range | die Veranstaltung, die Einrichtung oder der Nutzer, in dessen Dateibereich diese Datei liegt
+terms-of-use | die Lizenz, unter der diese Datei verfügbar gemacht wird
+
+### Meta
+
+In den Metadaten von Dateien ist der "download-link" enthalten, um den Inhalt der Datei herunterzuladen.
+
+
+### Schema "files"
+
+Anders als Ressourcen vom Typ "file-refs" sind Ressourcen vom Typ "files" über die grafische Oberfläche nicht verfügbar. Technisch werden "files" verwendet, um die Dateien tatsächlich auf der Festplatte (oder einem entfernten Speicherort) abzulegen.
+
+Erst durch die Verknüpfung durch "file-refs" werden Ressourcen vom Typ "files" sichtbar.
+
+### Attribute
+
+Attribut | Beschreibung
+--------- | ------------
+name | der Name der Datei
+mime-type | der MIME-Typ der Datei
+size | die Größe der Datei in Bytes
+storage | TODO
+mkdate | das Erstellungsdatum der Datei
+chdate | das Datum der letzten Änderung der Datei
+
+### Relationen
+
+ Relation | Beschreibung
+-------- | ------------
+file-refs | alle Ressourcen vom Typ "file-refs", die auf diese Datei verweisen
+owner | der Nutzer, dem diese Datei gehört
+
+### Type "folders"
+
+Ressourcen vom Typ "folders" sind im herkömmlichen Sinne Ordner und können weitere "folders" oder Ressourcen vom Typ "file-refs" enthalten.
+
+Es gibt verschiedene Arten von "folders". In Stud.IP werden aber vorrangig "StandardFolders" verwendet. Für diese sind alle Operationen möglich. Für andere Arten entscheiden die Implementierungen jeweils selbst, ob die Operation möglich ist.
+
+### Attribute
+
+Attribut | Beschreibung
+------------ | ------------
+folder-type | die Art des Ordners
+name | der Name des Ordners
+description | die Beschreibung des Ordners
+mkdate | das Erstellungsdatum des Ordners
+chdate | das Datum der letzten Änderung des Ordners
+is-visible | Darf der eingeloggte Nutzer den Ordner sehen?
+is-readable | Darf der eingeloggte Nutzer den Ordner öffnen?
+is-writable | Darf der eingeloggte Nutzer im Ordner Dateien erstellen?
+is-editable | Darf der eingeloggte Nutzer den Ordner bearbeiten?
+is-subfolder-allowed | Darf der eingeloggte Nutzer im Ordner weitere Ordner erstellen?
+
+
+### Relationen
+
+Relation | Beschreibung
+--------- | ------------
+owner | der Nutzer, dem dieser Ordner gehört
+parent | der Ordner, in dem sich dieser Ordner befindet
+range | die Veranstaltung, die Einrichtung oder der Nutzer, in dessen Dateibereich dieser Ordner liegt
+folders | die Ordner, die sich in diesem Ordner befinden
+file-refs | die Dateien, die sich in diesem Ordner befinden
+
+
+### Type "terms-of-use"
+
+Jede Datei unterliegt einer Lizenz, die die Nutzung, Weitergabe und Veränderung regelt.
+
+### Attribute
+
+Attribut | Beschreibung
+------------ | ------------
+name | der Name der Lizenz
+description | die Beschreibung der Lizenz
+icon | das für die Lizenz verwendete Icon
+mkdate | das Erstellungsdatum der Lizenz
+chdate | das Datum der letzten Änderung der Lizenz
+
+
+### Relationen
+
+Lizenzen ('terms-of-use') haben keine Relationen.
+
+## Alle Lizenzen
+
+```shell
+curl --request GET \
+ --url https://example.com/terms-of-use \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Diese Route erfragt alle im Stud.IP registrierten Lizenzen von Dateien.
+
+### HTTP Request
+
+`GET /terms-of-use`
+
+### Autorisierung
+
+Jeder Nutzer darf diese Route verwenden.
+
+## Eine Lizenz auslesen
+
+```shell
+curl --request GET \
+ --url https://example.com/terms-of-use/<ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route kann eine beliebige Lizenz ausgelesen werden.
+
+### HTTP Request
+
+`GET /terms-of-use/{id}`
+
+Parameter | Beschreibung
+--------- | ------------
+id | die ID der Lizenz
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Jeder Nutzer darf diese Route verwenden.
+
+
+## Alle Dateien eines Dateibereichs
+
+```shell
+curl --request GET \
+ --url https://example.com/<courses,institutes,users>/<ID>/file-refs \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route können *alle* Dateien eines Dateibereichs ausgelesen
+werden. Das Ergebnis ist eine flache Liste aller Dateien, ungeachtet
+der Zugehörigkeit zu Ordnern dieses Dateibereichs.
+
+### HTTP Request
+
+`GET /courses/{id}/file-refs`
+`GET /institutes/{id}/file-refs`
+`GET /users/{id}/file-refs`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID der Veranstaltung, der Einrichtung oder des Nutzers
+
+### URL-Parameter
+
+Parameter | Default | Beschreibung
+--------- | ------- | ------------
+page[offset] | 0 | der Offset (siehe Paginierung)
+page[limit] | 30 | das Limit (siehe Paginierung)
+
+### Autorisierung
+
+Die Dateien einer Einrichtung darf jeder Nutzer sehen. Die Dateien
+einer Veranstaltung sehen alle Nutzer, die Zugriff
+auf die Veranstaltung haben. Die Dateien eines Nutzers sehen alle, es
+sei denn der Nutzer ist unsichtbar.
+
+Im Übrigen gelten die Zugriffsregeln der Ordner, in denen die Dateien liegen.
+
+
+## Alle Ordner eines Dateibereichs
+
+```shell
+curl --request GET \
+ --url https://example.com/<courses,institutes,users>/<ID>/folders \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route können *alle* Ordner eines Dateibereichs ausgelesen
+werden. Das Ergebnis ist eine flache Liste aller Ordner, ungeachtet
+der Zugehörigkeit zu Ordnern dieses Dateibereichs.
+
+### HTTP Request
+
+`GET /courses/{id}/folders`
+`GET /institutes/{id}/folders`
+`GET /users/{id}/folders`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID der Veranstaltung, der Einrichtung oder des Nutzers
+
+### URL-Parameter
+
+Parameter | Default | Beschreibung
+--------- | ------- | ------------
+page[offset] | 0 | der Offset (siehe Paginierung)
+page[limit] | 30 | das Limit (siehe Paginierung)
+
+### Autorisierung
+
+Die Ordner einer Einrichtung darf jeder Nutzer sehen. Die Ordner einer
+Veranstaltung sehen alle Nutzer, die Zugriff auf die Veranstaltung
+haben. Die Ordner eines Nutzers sehen alle, es sei denn der Nutzer ist
+unsichtbar.
+
+Im Übrigen gelten die Zugriffsregeln der Ordner, in denen die Ordner liegen.
+
+
+## Einen Ordner erstellen
+
+Ein Ordner kann einfach über diese Route angelegt werden.
+
+```shell
+ curl --request POST \
+ --url https://example.com/courses/<ID>/folders \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_dozent:testing" | base64`" \
+ --data '{"data": {"type": "folders","attributes": {"name": "Name of the folder"}, "relationships": {"parent": {"data": {"type":"folders","id":"<any-folder-id>"}}}}}'
+```
+
+### HTTP Request
+
+`POST /courses/{id}/folders`
+`POST /institutes/{id}/folders`
+`POST /users/{id}/folders`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID der Veranstaltung, der Einrichtung oder des Nutzers
+
+Der Request-Body enthält ein "JSONAPI resource object" vom Typ "folders". Name und übergeordneter, enthaltender Ordner sind erforderlich: Das Attribut "name" und die Relation "parent", die auf ein "folders"-Objekt verweist, sind verpflichtend.
+
+### URL-Parameter
+
+keine Parameter
+
+### Autorisierung
+
+Ob ein Ordner angelegt werden darf, wird von der jeweiligen Implementation des Zielordners entschieden.
+
+
+## Eine Datei auslesen
+
+```shell
+curl --request GET \
+ --url https://example.com/file-refs/<ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route kann eine Datei ausgelesen werden.
+
+### HTTP Request
+
+`GET /file-refs/{id}`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID der Datei
+
+### URL-Parameter
+
+keine Parameter
+
+### Autorisierung
+
+Ob eine Datei ausgelesen werden darf, entscheidet der übergeordnete Ordner.
+
+
+## Metadaten einer Datei ändern
+
+```shell
+curl --request PATCH \
+ --url https://example.com/file-refs/<ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --header "Content-Type: application/vnd.api+json" \
+ --data '{"data": {"type": "file-refs","id":"<id-der-datei>", \
+ "attributes":{"name":"neuer-name.jpg"}}}'
+```
+Mit dieser Route kann der Name, die Beschreibung und/oder die Lizenz einer Datei geändert werden. Dazu wird JSONAPI-typisch das angepasste "resource object" an diese Route geschickt.
+
+### HTTP Request
+
+`PATCH /file-refs/{id}`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID der Datei
+
+Der Request-Body enthält das veränderte "resource object".
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Ob eine Datei angepasst werden darf, entscheidet der übergeordnete Ordner.
+
+
+## Eine Datei löschen
+
+```shell
+curl --request DELETE \
+ --url https://example.com/file-refs/<ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route löscht man eine Datei.
+
+### HTTP Request
+
+`DELETE /file-refs/{id}`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID der Datei
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+ob eine Datei gelöscht werden kann, entscheidet der übergeordnete Ordner.
+
+
+## Lizenz einer Datei auslesen
+```shell
+curl --request GET \
+ --url https://example.com/file-refs/<ID>/relationships/terms-of-use \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Um die Relation einer Datei zu einer Lizenz auszulesen, verwendet man diese Route.
+
+### HTTP Request
+
+`GET /file-refs/<ID>/relationships/terms-of-use`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID der Datei
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Der übergeordnete Ordner der Datei entscheidet.
+
+
+## Lizenz einer Datei ändern
+```shell
+curl --request PATCH \
+ --url https://example.com/file-refs/<ID>/relationships/terms-of-use \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --header "Content-Type: application/vnd.api+json" \
+ --data '{"data": {"type": "terms-of-use","id": "<id-der-lizenz>"}}'
+```
+
+Um die Relation einer Datei zu einer Lizenz zu ändern, verwendet man diese Route. Das Löschen der Relation zur Datei ist ausgeschlossen.
+
+### HTTP Request
+
+`PATCH /file-refs/<ID>/relationships/terms-of-use`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID der Datei
+
+Der Request-Body muss einen "resource identifier" von Typ "terms-of-use" enthalten.
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Der übergeordnete Ordner der Datei entscheidet.
+
+
+## Den ETag einer Datei auslesen
+
+:::danger
+Diese Route ist keine JSON-API-konforme Route.
+:::
+
+```shell
+curl --request HEAD \
+ --url https://example.com/file-refs/<ID>/content \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Um einen Fingerabdruck (ETag) des tatsächlichen Inhalts einer Datei zu bekommen, kann man diese nicht-JSON-API-Route aufrufen.
+
+### HTTP Request
+
+`HEAD /file-refs/{id}/content`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID der Datei
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Der übergeordnete Ordner der Datei entscheidet.
+
+
+## Eine Datei herunterladen
+
+:::danger
+Diese Route ist keine JSON-API-konforme Route.
+:::
+
+```shell
+curl --request GET \
+ --url https://example.com/file-refs/<ID>/content \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route kann der Inhalt einer Datei heruntergeladen werden.
+
+### HTTP Request
+
+`GET /file-refs/{id}/content`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID der Datei
+
+Der Request kann einen ETag-Header mitbringen, um redundante
+Datenübertragung zu vermeiden.
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Der übergeordnete Ordner der Datei entscheidet.
+
+
+## Inhalt einer Datei aktualisieren
+
+:::danger
+Diese Route ist keine JSON-API-konforme Route.
+:::
+
+```shell
+curl --request POST --url https://example.com/file-refs/<ID>/content \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ -F 'myfile=@/path/to/local/file'
+```
+
+Mit dieser Route kann der Inhalt einer vorhandenen Datei überschrieben
+werden. Dazu wird eine einzige Datei "multipart/form-data"-kodiert an
+diese Route geschickt.
+
+### HTTP Request
+
+`POST /file-refs/{id}/content`
+
+Im Request-Body muss dann eine Datei "multipart/form-data"-kodiert
+enthalten sein.
+
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Der übergeordnete Ordner der Datei entscheidet.
+
+
+## Einen Ordner auslesen
+
+```shell
+curl --request GET \
+ --url https://example.com/folders/<ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route kann man die Daten eines Ordners auslesen.
+
+### HTTP Request
+
+`GET /folders/{id}`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID des Ordners
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Die Art des Ordners entscheidet über die Autorisierung.
+
+
+## Einen Ordner ändern
+```shell
+curl --request PATCH \
+ --url https://example.com/folders/<ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --header "Content-Type: application/vnd.api+json" \
+ --data '{"data": {"type":"folders","id":"<id-der-lizenz>", \
+ "attributes":{"name":"Neuer Name"}}}'
+```
+
+Mit dieser Route kann der Name und/oder die Beschreibung geändert
+werden. Außerdem kann man den Ordner in einen anderen Ordner verschieben. Dazu
+ändert man die "parent"-Relation.
+
+### HTTP Request
+
+`PATCH /folders/{id}`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID des Ordners
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Die Art des Ordners entscheidet über die Autorisierung.
+
+
+## Einen Ordner löschen
+```shell
+curl --request DELETE \
+ --url https://example.com/folders/<ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route kann man einen Ordner löschen.
+
+### HTTP Request
+
+`DELETE /folders/{id}`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID des Ordners
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Die Art des Ordners entscheidet über die Autorisierung.
+
+
+## Alle Dateien eines Ordners
+```shell
+curl --request GET \
+ --url https://example.com/folders/<ID>/file-refs \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route erhält man eine Liste aller Dateien, die direkt in
+einem Ordner liegen.
+
+### HTTP Request
+
+`GET /folders/{id}/file-refs`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID des Ordners
+
+### URL-Parameter
+
+Parameter | Default | Beschreibung
+--------- | ------- | ------------
+page[offset] | 0 | der Offset (siehe Paginierung)
+page[limit] | 30 | das Limit (siehe Paginierung)
+
+### Autorisierung
+
+Ob man die Liste der Dateien eines Ordners sehen darf, entscheidet die
+Implementierung des Ordners.
+
+
+## Alle Ordner eines Ordners
+```shell
+curl --request GET \
+ --url https://example.com/folders/<ID>/folders \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route erhält man eine Liste aller Ordner, die direkt in
+einem Ordner liegen.
+
+### HTTP Request
+
+`GET /folders/{id}/folders`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID des Ordners
+
+### URL-Parameter
+
+Parameter | Default | Beschreibung
+--------- | ------- | ------------
+page[offset] | 0 | der Offset (siehe Paginierung)
+page[limit] | 30 | das Limit (siehe Paginierung)
+
+### Autorisierung
+
+Ob man die Liste der Ordner eines Ordners sehen darf, entscheidet die
+Implementierung des Ordners.
+
+
+## Eine Datei erstellen
+
+Eine Datei wird immer in einem Ordner erstellt. Da Dateien aus
+Metadaten **und** Inhalt bestehen, muss das Erstellen einer Datei in
+zwei Schritten passieren. Dazu kann entweder
+
+* zuerst der Inhalt hochgeladen werden und dann die Metadaten (wie Beschreibung und Lizenz) angepasst werden oder
+* erst die Datei mit den Metadaten erstellt werden und nachträglich der Inhalt hochgeladen werden.
+
+### Variante a.
+
+```shell
+curl --request POST --url "https://example.com/folders/<ID>/file-refs" \
+ -F 'file=@/pfad/zu/einer-neuen-datei.jpg' \
+ --header "Authorization: Basic `echo -ne "test_dozent:testing" | base64`"
+```
+
+Zuerst sendet man einen ``POST``-Request mit ``Content-Type: multipart/form-data`` und der Datei im Request-Body an die angegebene URL.
+
+Man erhält im Erfolgsfall einen Status-Code 201 und einen ``Location``-Header, der einen zum neu erstellten Dokument in die JSON:API bringt.
+
+Der *Dateiname* wird standardmäßig aus dem Upload genommen und auch für den Namen der Datei verwendet.
+
+Will man einen anderen Dateinamen verwenden, kann man einen HTTP-Header verwenden: ``Slug: neuer-dateiname.txt``.
+
+Über die URL aus dem erhaltenen ``Location``-Header erhält man die JSON:API-Repräsentation der hochgeladenen Datei.
+
+Nun können mit einem (JSON:API-typischen) ``PATCH``-Request an diese Route Modifikationen an den Metadaten (wie Beschreibung usw.) vorgenommen werden.
+
+### Variante b.
+
+```shell
+curl --request POST --url https://example.com/folders/<ID>/file-refs \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data '{"data": { "type": "file-refs", "attributes": { "name": "iason.txt", "description": "Iasons Lebenslauf"}, "relationships":{"terms-of-use": {"data": {"type": "terms-of-use", "id": "FREE_LICENSE"}}}}}'
+```
+
+Zunächst sendet man einen ``POST``-Request mit ``Content-Type: application/vnd.api+json`` an die URL.
+
+Im Request-Body muss dann eine JSON:API-typische Repräsentation der neuen Datei enthalten sein. Im Erfolgsfall erhält man dann eine Repräsentation der neu angelegten Datei, die aber derzeit noch keinen Inhalt hat.
+
+Daher muss der Inhalt in einem zweiten Request hochgeladen werden. Dazu wird – wie unter "Inhalt einer Datei aktualisieren" beschrieben – ein ``POST``-Request an die ``download-url`` geschickt.
+
+### HTTP Request
+`POST /folders/{id}/file-refs`
+
+Im Request-Body befindet sich dann entweder eine
+"multipart/form-data"-kodierte Datei oder ein JSON-API-spezifisches
+"resource object".
+
+Wenn man ein JSON-API-"resource object" verschickt, **muss** die
+Relation `terms-of-use` (die Lizenz) enthalten sein. Ohne Lizenz
+können keine Dateien angelegt werden.
+
+### URL-Parameter
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID des Ordners
+
+### Authorisierung
+
+Ob man eine Datei erstellen darf, entscheidet die Implementierung des Ordners.
+
+## Eine Datei kopieren
+
+Um eine Datei zu kopieren, verwendet man die ["Variante b."](#variante-b) für das
+Anlegen von Dateien.
+
+Zuerst benötigt man den "resource identifier" der Relation `file` der
+zu kopierenden Datei. Dann schickt man ein JSON-API-"resource object"
+an die URL zum Erstellen einer Datei und setzt dort diesen "resource
+identifier" als Relation `file` der neuen Datei.
+
+Wenn man selbst der Besitzer der Quelldatei ist, bleibt der Verweis
+auf das `file` bestehen. Ist man nicht der Besitzer der Quelldatei,
+wird auch das `file` kopiert und man selbst dessen Besitzer.
+
+## Einen Ordner kopieren
+
+:::danger
+Diese Route ist keine JSON-API-konforme Route.
+:::
+
+```shell
+curl -F "destination=<destination-ID>" \
+ --url "https://example.com/folders/<source-ID>/copy" \
+ --header "Authorization: Basic `echo -ne "test_dozent:testing" | base64`"
+```
+
+Um einen Ordner zu kopieren, wird diese Route verwendet, die
+allerdings nicht JSON-API-konform ist. Dazu wird ein POST-Request an
+die Route des Ordners geschickt, in deren Request-Body der Zielordner
+spezifiziert wird. Der Request-Body muss vom
+"multipart/form-data"-kodiert sein.
+
+### HTTP Request
+
+`POST /folders/{id}/copy`
+
+Der "Content-Type" des Requests muss "multipart/form-data" sein. Im
+Request-Body muss unter dem Schlüssel "destination" die ID des
+Zielordners enthalten.
+
+Wenn der Request erfolgreich war, bekommt man einen Status-Code 201
+und einen `Location`-Header, der auf den neuen, kopierten Ordner
+zeigt.
+
+### Authorisierung
+Jeder Nutzer, der den Quellordner öffnen und im Zielordner schreiben
+darf, kann diese Route aufrufen.
+
+
+## Einen Ordner erstellen
+```shell
+curl --request POST \
+ --url https://example.com/folders/<ID>/folders \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data '{"data": { "type": "folders", "attributes": {"name":"Neuer Ordner"}}}'
+```
+
+Mit dieser Route kann man einen neuen Ordner anlegen.
+
+### HTTP Request
+
+`POST /folders/{id}/folders`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID des übergeordneten Ordners
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Ob man einen Ordner erstellen darf, entscheidet die Implementierung des übergeordneten Ordners.
+
+
+## Ein "File" auslesen
+```shell
+curl --request GET \
+ --url https://example.com/files/<ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Die Dateien, die in den obigen Routen genannt werden, sind technisch gesehen nur Verweise auf tatsächliche Dateien auf der Festplatte o.ä. Auch die tatsächlichen Dateien ("files") können ausgelesen werden. Dazu verwendet man diese Route.
+
+### HTTP Request
+
+`GET /files/{id}`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID des "files"
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Ein "file" kann ein Nutzer dann sehen, wenn eine der darauf verweisenden Dateien vom Nutzer gesehen werden kann.
+
+
+## Alle Dateien eines "Files"
+```shell
+curl --request GET \
+ --url https://example.com/files/<ID>/file-refs \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route können alle Dateien, die auf dieses "file" verweisen, ausgelesen werden.
+
+### HTTP Request
+
+`GET /files/{id}/file-refs`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID des "files"
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Die Route kann sinnvoll aufgerufen werden, wenn man eine der darauf verweisenden Dateien sehen kann.
+
+
+## Alle Datei-IDs eines "Files"
+```shell
+curl --request GET \
+ --url https://example.com/files/<ID>/relationships/file-refs \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Diese Route wird verwendet, um alle IDs der Dateien zu erhalten, die auf dieses "file" verweisen.
+
+### HTTP Request
+
+`GET /files/{id}/relationships/file-refs`
+
+Parameter | Beschreibung
+--------- | -------
+id | die ID des "files"
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Die Route kann sinnvoll aufgerufen werden, wenn man eine der auf das "file" verweisenden Dateien sehen kann.
diff --git a/docs/docs/jsonapi/forum.md b/docs/docs/jsonapi/forum.md
new file mode 100644
index 0000000..bb3090a
--- /dev/null
+++ b/docs/docs/jsonapi/forum.md
@@ -0,0 +1,626 @@
+---
+title: Forum
+---
+
+
+Das Stud.IP-Forum bietet die Möglichkeit Beiträge zu erstellen, zu kommentieren
+und zu Kategorisieren. Jedes Forum ist an genau eine Veranstaltung gebunden.
+Die Schema's werden in Forum-Categories und Forum-Entries unterteilt.
+
+## Schema "forum-categories"
+
+Kategorien für Einträge haben einen Namen und geben die Hierarchie des Forums
+anhand ihrer Position an.
+
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+title | Name einer Kategorie
+position | Position einer Kategorie
+
+### Relationen
+
+Relation | Beschreibung
+-------- | ------------
+course | Der Kurs des Forums, indem die Kategorie angelegt ist
+entries | Alle Forum-Einträge einer Forum-Kategorie
+
+## Schema "forum-entries"
+
+Einträge des Forums liegen auf verschiedenen Ebenen. Sie können direkt in
+Kategorien als Themen erstellt werden oder an vorhandene Einträge angebunden
+werden.
+
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+title | Name eines Entries (sollte nur bei Themen angezeigt werden)
+content | Gibt den Inhalt eines Entries wieder
+area | Dieses Attribut wird mitgeführt (ist aber idr. '0')
+
+### Relationen
+
+Relation | Beschreibung
+-------- | ------------
+category | Die Forum-Kategorie des Forumeintrags
+entries | Alle Untereinträge eines Forumeintrags
+
+## Alle Forum-Kategorien eines Kurses auslesen
+ GET /courses/{id}/forum-categories
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID des Kurses
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/courses/<COURSE-ID>/forum-categories \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ ```
+
+### Autorisierung
+ Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
+ > Der Request liefert JSON ähnlich wie dieses:
+
+```json
+{
+ "data": [
+ {
+ "type": "forum-categories",
+ "id": "d6b887a73f024cf31b4a01f41531b809",
+ "attributes": {
+ "title": "NewStuff",
+ "position": 0
+ },
+ "relationships": {
+ "course": {
+ "data": {
+ "type": "courses",
+ "id": "1b7d3834e42c1569947e0eab7b63ed19"
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/courses/1b7d3834e42c1569947e0eab7b63ed19"
+ }
+ }
+ },
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/forum-categories/d6b887a73f024cf31b4a01f41531b809"
+ }
+ },
+ {
+ "type": "forum-categories",
+ "id": "3710de2efd59869ab7ed7e410f70947f",
+ "attributes": {
+ "title": "CatCreateRoute",
+ "position": 1
+ },
+ "relationships": {
+ "course": {
+ "data": {
+ "type": "courses",
+ "id": "1b7d3834e42c1569947e0eab7b63ed19"
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/courses/1b7d3834e42c1569947e0eab7b63ed19"
+ }
+ }
+ },
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/forum-categories/3710de2efd59869ab7ed7e410f70947f"
+ }
+ },
+ {
+ "type": "forum-categories",
+ "id": "7684942d4a1d3f8ab0752165e22c31a6",
+ "attributes": {
+ "title": "TESTECASE ",
+ "position": 2
+ },
+ "relationships": {
+ "course": {
+ "data": {
+ "type": "courses",
+ "id": "1b7d3834e42c1569947e0eab7b63ed19"
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/courses/1b7d3834e42c1569947e0eab7b63ed19"
+ }
+ }
+ },
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/forum-categories/7684942d4a1d3f8ab0752165e22c31a6"
+ }
+ },
+ {
+ "type": "forum-categories",
+ "id": "4ca16225a42c94957c4129da5f0bef2d",
+ "attributes": {
+ "title": "CatCreateRoute",
+ "position": 3
+ },
+ "relationships": {
+ "course": {
+ "data": {
+ "type": "courses",
+ "id": "1b7d3834e42c1569947e0eab7b63ed19"
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/courses/1b7d3834e42c1569947e0eab7b63ed19"
+ }
+ }
+ },
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/forum-categories/4ca16225a42c94957c4129da5f0bef2d"
+ }
+ },
+ {
+ "type": "forum-categories",
+ "id": "1b7d3834e42c1569947e0eab7b63ed19",
+ "attributes": {
+ "title": "Allgemein",
+ "position": 4
+ },
+ "relationships": {
+ "course": {
+ "data": {
+ "type": "courses",
+ "id": "1b7d3834e42c1569947e0eab7b63ed19"
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/courses/1b7d3834e42c1569947e0eab7b63ed19"
+ }
+ }
+ },
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/forum-categories/1b7d3834e42c1569947e0eab7b63ed19"
+ }
+ }
+ ]
+}
+```
+
+## Eine Forum-Kategorie auslesen
+ GET /forum-categories/{id}
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID der Kategorie
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/forum-categories/<FORUM-CATEGORY-ID> \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ ```
+### Autorisierung
+ Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
+
+ > Der Request liefert JSON ähnlich wie dieses:
+
+```json
+{
+"data": {
+"type": "forum-categories",
+"id": "1b7d3834e42c1569947e0eab7b63ed19",
+"attributes": {
+"title": "Allgemein",
+"position": 4
+},
+"relationships": {
+"course": {
+"data": {
+"type": "courses",
+"id": "1b7d3834e42c1569947e0eab7b63ed19"
+},
+"links": {
+"related": "/stud35/plugins.php/argonautsplugin/courses/1b7d3834e42c1569947e0eab7b63ed19"
+}
+}
+},
+"links": {
+"self": "/stud35/plugins.php/argonautsplugin/forum-categories/1b7d3834e42c1569947e0eab7b63ed19"
+}
+}
+}
+```
+
+## Einen Forum-Eintrag auslesen
+ GET /forum-entries/{id}
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID des Entries
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/forum-entries/<FORUM-ENTRY-ID> \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ ```
+### Autorisierung
+ Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
+
+ > Der Request liefert JSON ähnlich wie dieses:
+
+```json
+{
+ "data": {
+ "type": "forum-entries",
+ "id": "1b7d3834e42c1569947e0eab7b63ed19",
+ "attributes": {
+ "title": "Übersicht",
+ "area": 0,
+ "content": ""
+ },
+ "relationships": {
+ "category": {
+ "data": {
+ "type": "forum-categories",
+ "id": null
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/forum-categories/"
+ }
+ },
+ "child-entries": {
+ "data": [
+ {
+ "type": "forum-entries",
+ "id": "2e6ff68d79c5e3f3ed24bd0274865e42"
+ },
+ {
+ "type": "forum-entries",
+ "id": "3e0ec8e69afe2502763730e17954d340"
+ },
+ {
+ "type": "forum-entries",
+ "id": "783e1b783a76f109eeb5fc19c43c2d08"
+ },
+ {
+ "type": "forum-entries",
+ "id": "9af47a2c1463b12a35e3a7c3a10bd53c"
+ },
+ {
+ "type": "forum-entries",
+ "id": "a5e119fc5b8cfc549ab9cc985e8609a1"
+ },
+ {
+ "type": "forum-entries",
+ "id": "b21ca75f9562d3a5751babaac49bbc9a"
+ },
+ {
+ "type": "forum-entries",
+ "id": "c2e21dfa7d071fb6f40ed29271f926aa"
+ },
+ {
+ "type": "forum-entries",
+ "id": "f5f8ea3da6fd945eb92dd0d1e1193132"
+ }
+ ],
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/forum-entries/1b7d3834e42c1569947e0eab7b63ed19/child-entries"
+ }
+ }
+ },
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/forum-entries/1b7d3834e42c1569947e0eab7b63ed19"
+ }
+ }
+}
+```
+
+## Alle Forum-Einträge einer Kategorie auslesen
+ GET /forum-categories/{id}/entries
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID der Kategorie
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/forum-categories/<CATEGORY-ID>/entries \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ ```
+### Autorisierung
+ Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
+
+ > Der Request liefert JSON ähnlich wie dieses:
+
+```json
+{
+ "data": {
+ "type": "forum-entries",
+ "id": "1b7d3834e42c1569947e0eab7b63ed19",
+ "attributes": {
+ "title": "Übersicht",
+ "area": 0,
+ "content": ""
+ },
+ "relationships": {
+ "category": {
+ "data": {
+ "type": "forum-categories",
+ "id": null
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/forum-categories/"
+ }
+ },
+ "child-entries": {
+ "data": [
+ {
+ "type": "forum-entries",
+ "id": "2e6ff68d79c5e3f3ed24bd0274865e42"
+ },
+ {
+ "type": "forum-entries",
+ "id": "3e0ec8e69afe2502763730e17954d340"
+ },
+ {
+ "type": "forum-entries",
+ "id": "783e1b783a76f109eeb5fc19c43c2d08"
+ },
+ {
+ "type": "forum-entries",
+ "id": "9af47a2c1463b12a35e3a7c3a10bd53c"
+ },
+ {
+ "type": "forum-entries",
+ "id": "a5e119fc5b8cfc549ab9cc985e8609a1"
+ },
+ {
+ "type": "forum-entries",
+ "id": "b21ca75f9562d3a5751babaac49bbc9a"
+ },
+ {
+ "type": "forum-entries",
+ "id": "c2e21dfa7d071fb6f40ed29271f926aa"
+ },
+ {
+ "type": "forum-entries",
+ "id": "f5f8ea3da6fd945eb92dd0d1e1193132"
+ }
+ ],
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/forum-entries/1b7d3834e42c1569947e0eab7b63ed19/child-entries"
+ }
+ }
+ },
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/forum-entries/1b7d3834e42c1569947e0eab7b63ed19"
+ }
+ }
+}
+```
+
+## Alle Untereinträge eines Forumeintrags auslesen
+ GET /forum-entries/{id}/entries
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID des Eintrags
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/forum-entries/<FORUM-ENTRY-ID>/entries \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ ```
+### Autorisierung
+ Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
+
+__
+## Eine Kategorie innerhalb eines Kurses anlegen
+
+ POST /courses/{id}/forum-categories
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID des Kurses
+
+ ```shell
+ curl --request POST \
+ --url https://example.com/courses/<COURSE-ID>/categories \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ '{"data": {"type": "forum-categories","attributes": {"title": "CreateCategoryTest","content": "works"}
+ }
+}'
+
+ ```
+### Autorisierung
+ Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
+
+ > Der Request liefert JSON ähnlich wie dieses:
+
+```json
+{
+"data": {
+"type": "forum-categories",
+"id": "1b7d3834e42c1569947e0eab7b63ed19",
+"attributes": {
+"title": "Allgemein",
+"position": 4
+},
+"relationships": {
+"course": {
+"data": {
+"type": "courses",
+"id": "1b7d3834e42c1569947e0eab7b63ed19"
+},
+"links": {
+"related": "/stud35/plugins.php/argonautsplugin/courses/1b7d3834e42c1569947e0eab7b63ed19"
+}
+}
+},
+"links": {
+"self": "/stud35/plugins.php/argonautsplugin/forum-categories/1b7d3834e42c1569947e0eab7b63ed19"
+}
+}
+}
+```
+
+## Einen Eintrag in eine Kategorie posten
+
+ POST /forum-categories/{id}/entries
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID der Kategorie
+
+ ```shell
+ curl --request POST \
+ --url https://example.com/forum-entries/<FORUM-CATEGORY-ID>/entries \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ '{"data": {"type": "forum-entries","attributes": {"title": "TestTheRoute","content": "works!"}}}'
+
+ ```
+### Autorisierung
+ Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
+
+
+## Einen Eintrag unter einen Eintrag posten
+
+ POST /forum-entries/{id}/entries
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID des Eintrags
+
+ ```shell
+ curl --request POST \
+ --url https://example.com/forum-entries/<FORUM-ENTRY-ID>/entries \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ '{"data": {"type": "forum-entries","attributes": {"title": "TestTheRoute","content": "works!"}}}'
+
+ ```
+### Autorisierung
+ Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
+
+
+## Einen Kategorie aktualisieren
+
+ PATCH /forum-categories/{id}
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID der Kategorie
+
+ ```shell
+ curl --request PATCH \
+ --url https://example.com/forum-categories/<FORUM-CATEGORY-ID> \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ '{"data": {"type": "forum-categories","attributes": {"title": "UpdateCategory","content": "time for a change"}
+
+ ```
+### Autorisierung
+ Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
+ Der Nutzer sollte die entsprechenden Adminrechte verfügen oder Ersteller der
+ Kategorie sein
+
+ > Der Request liefert JSON ähnlich wie dieses:
+
+```json
+{
+"data": {
+"type": "forum-categories",
+"id": "1b7d3834e42c1569947e0eab7b63ed19",
+"attributes": {
+"title": "Allgemein",
+"position": 4
+},
+"relationships": {
+"course": {
+"data": {
+"type": "courses",
+"id": "1b7d3834e42c1569947e0eab7b63ed19"
+},
+"links": {
+"related": "/stud35/plugins.php/argonautsplugin/courses/1b7d3834e42c1569947e0eab7b63ed19"
+}
+}
+},
+"links": {
+"self": "/stud35/plugins.php/argonautsplugin/forum-categories/1b7d3834e42c1569947e0eab7b63ed19"
+}
+}
+}
+```
+
+## Einen Forum-Eintrag aktualisieren
+
+ PATCH /forum-entries/{id}
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID des Eintrags
+
+ ```shell
+ curl --request PATCH \
+ --url https://example.com/forum-entries/<FORUM-ENTRY-ID> \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ '{"data": {"type": "forum-entries","attributes": {"title": "Update an entry","content": "time for a change"}}}'
+
+ ```
+ ### Autorisierung
+ Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
+ Der Nutzer sollte die entsprechenden Adminrechte verfügen oder Ersteller des
+ Eintrags sein
+
+## Eine Forum-Kategorie entfernen
+
+ DELETE /forum-categories/{id}
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID der Kategorie
+
+ ```shell
+ curl --request DELETE \
+ --url https://example.com/forum-categories/<FORUM-CATEGORY-ID> \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ ```
+### Autorisierung
+ Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
+ Der Nutzer sollte die entsprechenden Adminrechte verfügen oder Ersteller der
+ Kategorie sein.
+
+## Einen Forum-Eintrag entfernen
+
+ DELETE /forum-categories/{id}
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID des Eintrags
+
+```shell
+curl --request DELETE \
+ --url https://example.com/forum-entries/<FORUM-ENTRY-ID> \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+```
+### Autorisierung
+ Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
+ Der Nutzer sollte die entsprechenden Adminrechte verfügen oder Ersteller des
+ Eintrags sein.
diff --git a/docs/docs/jsonapi/institutes.md b/docs/docs/jsonapi/institutes.md
new file mode 100644
index 0000000..f5ce360
--- /dev/null
+++ b/docs/docs/jsonapi/institutes.md
@@ -0,0 +1,165 @@
+---
+title: Einrichtungen
+---
+
+
+Die Einrichtungen der Stud.IP-Installation können mit den folgenden Routen
+abgefragt werden.
+
+## Schema 'institutes'
+
+Alle Einrichtungen werden in Stud.IP mit diesem Schema abgebildet. Die `id`
+entspricht der in Stud.IP verwendeten `Institut_id`. Der Typ ist `institutes`.
+
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+name | der Einrichtungsname
+city | die Stadt in der die Einrichtung liegt
+street | die Anschrift (Straße) der Einrichtung
+phone | die Telefonnummer der Einrichtung
+fax | die Faxnummer der Einrichtung
+url | die URL der Webseite der Einrichtung
+mkdate | das Erstellungsdatum der Einrichtung in Stud.IP
+chdate | das letztes Änderungsdatum der Einrichtungsdaten in Stud.IP
+
+### Relationen
+
+keine Relationen
+
+
+## Schema 'institute-memberships'
+
+Die Mitgliedschaft in einer Einrichtung wird in Stud.IP mit diesem
+Schema abgebildet.
+
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+permission | die Rolle des Nutzers in der Einrichtung
+office-hours | die Sprechzeiten des Nutzers bzgl. der Einrichtung
+location | der Raum/Ort des Nutzers bzgl. der Einrichtung
+phone | die Telefonnummer des Nutzers bzgl. der Einrichtung
+fax | die Faxnummer des Nutzers bzgl. der Einrichtung
+
+### Relationen
+
+Relation | Beschreibung
+--------- | ------------
+institute | die Einrichtung dieser Mitgliedschaft
+user | der Nutzer dieser Mitgliedschaft
+
+## Alle Einrichtungen
+
+Dieser Endpoint liefert alle Einrichtungen im Stud.IP, die der
+JSON:API-Nutzer mit seinen ``credentials`` auch im Stud.IP selbst
+sehen darf. Die Ausgabe erfolgt paginiert und kann durch Angabe von
+Offset und Limit weitergeblättert werden.
+
+### HTTP Request
+
+ `GET /institutes`
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/institutes \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ ```
+
+### URL-Parameter
+
+Parameter | Default | Beschreibung
+--------- | ------- | ------------
+page[offset] | 0 | der Offset
+page[limit] | 30 | das Limit
+
+### Authorisierung
+
+Jeder Nutzer darf diese Route verwenden.
+
+
+## Eine Einrichtung
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/institutes/<INSTITUTE-ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ ```
+
+Eine bestimmte Einrichtung kann einfach über diese Route ausgelesen werden.
+
+### HTTP Request
+
+ `GET /institutes/{id}`
+
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID des Instituts
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Authorisierung
+
+Jeder Nutzer darf diese Route verwenden.
+
+
+## Mitgliedschaften in einer Einrichtung
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/institutes/<institute-id>/memberships \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+Gibt alle Mitgliedschaften mit den jeweiligen Daten der Nutzer zurück.
+
+### HTTP Request
+ `GET /institutes/{id}/memberships`
+
+### Parameter
+
+Parameter | Beschreibung
+--------- | -------
+id | ID der Einrichtung
+
+### URL-Parameter
+
+Parameter | Default | Beschreibung
+--------- | ------- | ------------
+filter[permission] | - | Rolle des Nutzers in der Einrichtung
+
+### Autorisierung
+
+Jeder Nutzer darf diese Route verwenden.
+
+
+## Eine Mitgliedschaft
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/institute-memberships/<ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ ```
+
+Mit dieser Route kann man eine Mitgliedschaft in einer Einrichtung auslesen.
+
+### HTTP Request
+
+`GET /institute-memberships/{id}`
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID der Mitgliedschaft
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Authorisierung
+
+Jeder Nutzer darf diese Route verwenden.
diff --git a/docs/docs/jsonapi/messages.md b/docs/docs/jsonapi/messages.md
new file mode 100644
index 0000000..e884e46
--- /dev/null
+++ b/docs/docs/jsonapi/messages.md
@@ -0,0 +1,144 @@
+---
+title: Nachrichten
+---
+
+:::info
+Studip bietet die Möglichkeit Nachrichten ("messages")
+innerhalb des Systems zwischen Nutzern und Nutzergruppen zu versenden.
+Das Nachrichten-System ist aufgebaut wie ein interner Mail-Service.
+:::
+
+## Schema "messages"
+
+Die Bestandteile einer Nachricht sind mit denen einer typischen Mail gleichzusetzen.
+
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+subject | Der Betreff einer Nachricht
+message | Der Content einer Nachricht
+mkdate | Erstellungsdatum einer Nachricht
+priority | Art der Relevanz
+tags | Themen der Nachricht
+
+### Relationen
+
+Relation | Beschreibung
+-------- | ------------
+sender | Absendender Nutzer
+recipients | Emfpänger einer Nachricht
+
+## Alle Inbox-Nachrichten
+
+Gibt alle Nachrichten eines Nutzers zurück.
+
+### HTTP Request
+
+ `GET /users/{id}/inbox`
+
+#### Parameter
+
+Parameter | Beschreibung
+--------- | -------
+id | ID des Nutzers
+
+#### URL-Parameter
+
+Parameter | Beschreibung
+--------- | ------------
+filter[unread] | Sollen nur ungelesene Nachrichten ausgeliefert werden?
+
+Wenn "filter[unread]" nicht gesetzt ist, werden alle Nachrichten ausgeliefert. Mit "filter[unread]=1" werden nur ungelesene Nachrichten zurück gegeben.
+
+### Authorisierung
+
+Diese Route kann nur vom Besitzer der betreffenden Nachrichten genutzt werden.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/blubber-postings/<posting-id>/users/<user-id>/inbox \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+## Alle Outbox-Nachrichten
+Gibt alle Outbox-Nachrichten eines Nutzers zurück
+
+### HTTP Request
+ `GET /users/{id}/outbox`
+
+### Parameter
+
+Parameter | Beschreibung
+--------- | -------
+id | ID des Nutzers
+
+### Authorisierung
+
+Diese Route kann nur vom Besitzer der betreffenden Nachrichten genutzt werden.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/blubber-postings/<posting-id>/users/<user-id>/outbox \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+## Eine Nachricht senden
+
+### HTTP Request
+
+`POST /messages`
+
+### Authorisierung
+
+Diese Route kann von jedem Studip-Nutzer genutzt werden.
+
+### Parameter
+
+Diese Route benötigt keine Parameter
+
+ ```shell
+ curl --request POST \
+ --url https://example.com/messages \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "root@studip:testing" | base64`" \
+ --data
+ '{"data": {"type": "messages","attributes": {"subject": "Eine neue E-Mail","message": "Das ist meine erste Mail - Dank der API super einfach.", "priority": "normal" }, "relationships": {"recipients": {"data": [{"type": "users","id": "6235c46eb9e962866ebdceece739ace5"}]}}}}'
+ ```
+
+## Eine Nachricht ansehen
+
+### HTTP Request
+
+ `GET /messages/{id}`
+
+ Parameter | Beschreibung
+ --------- | -------
+ id | ID der Nachricht
+
+### Authorisierung
+
+Diese Route kann von Besitzern der jeweiligen Nachricht genutzt werden.
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/blubber-postings/messages/<message-id>/ \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+ ```
+
+
+## Eine Nachricht löschen
+Löscht eine Nachrichten
+
+### HTTP Request
+ `DELETE /messages/{id}`
+
+### Authorisierung
+
+Diese Route kann von Besitzern der jeweiligen Nachricht genutzt werden.
+
+ ```shell
+ curl --request DELETE \
+ --url https://example.com/messages/<messages-id> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ ```
diff --git a/docs/docs/jsonapi/news.md b/docs/docs/jsonapi/news.md
new file mode 100644
index 0000000..ca95340
--- /dev/null
+++ b/docs/docs/jsonapi/news.md
@@ -0,0 +1,633 @@
+---
+title: Ankündigungen (News)
+---
+
+:::info
+ Ankündigungen informieren Stud.IP-Nutzer über neuste Ereignisse rund um
+ die Lehre. In Stud.IP können Ankündigungen z.B. systemweit (global) oder für
+ einen bestimmten Nutzerkreis erstellt werden.
+:::
+
+## Schema "news"
+
+Ankündigungen bestehen aus ihrem Inhalt und einigen Meta-Daten. Die Dauer der
+Sichtbarkeit einer Anküdnigung wird durch ihre Attribute publication-start und
+end bestimmt (siehe Relationen).
+
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+title | Name einer news
+content | Inhalt einer News
+mkdate | Erstellungs-Datum einer News
+chdate | Datum der letzten Änderung
+publication-start | Start der Sichtbarkeit für den Nutzerkreis einer News
+publication-end | Ende der Sichtbarkeit für den Nutzerkreis einer News
+comments-allowed | Bestimmung, ob Kommentare erlaubt sind (Boolean)
+
+Ein Beispiel zum erstellen einer News anhand des Schemas folgt in News anlegen.
+
+### Relationen
+
+ Relation | Beschreibung
+-------- | ------------
+author | Ersteller einer News
+ranges | global, institute, semester, course, users
+
+Der Range einer News gibt an wo sie publiziert wird und somit auch für wen
+sie sichtbar ist.
+
+## Schema "comments"
+
+Kommentare werden in Stud.IP an eine Ankündigung angehangen, wenn der Ersteller
+der News die Erlaubnis vergeben hat.
+
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+content | Inhalt eines Kommentars
+mkdate | Erstellungs-Datum
+chdate | Datum der letzten Änderung
+
+### Relationen
+
+ Relation | Beschreibung
+-------- | ------------
+author | Der Ersteller des Kommentars
+news | Die kommentierte News
+
+## News anlegen
+ Das Anlegen einer News ist in verschiedenen Kontexten möglich. Sie kann
+ global als systemweite News, kursintern oder nutzerbezogen angelegt werden.
+
+## Eine globale News anlegen
+
+### Route
+ `POST /news`
+### Autorisierung
+Die Erstellung einer globalen News erfordern zur Zeit noch Root-Rechte.
+Es wird diskutiert ob hier Adminrechte reichen.
+
+ ```shell
+ curl --request POST \
+ --url https://example.com/news \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "root@studip:testing" | base64`" \
+ --data
+ '{"data": {"type": "news","attributes": {"title": "Neue News","comments-allowed": true,"publication-start": "2020-01-01T12:12:12+00:00","publication-end": "2021-01-01T12:12:12+00:00","content": "Eine neue News sieht das Tageslicht."}}}'
+ ```
+
+## Eine Kurs-News anlegen
+
+ `POST /courses/{id}/news`
+
+ ```shell
+ curl --request POST \
+ --url https://example.com/courses/<COURSE-ID>/news \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_dozent:testing" | base64`" \
+ --data
+ '{"data": {"type": "news","attributes": {"title": "Neue News","comments-allowed": true,"publication-start": "2020-01-01T12:12:12+00:00","publication-end": "2021-01-01T12:12:12+00:00","content": "Eine neue News sieht das Tageslicht."}}}'
+ ```
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID des Kurses
+### Autorisierung
+Der Nutzer muss mindestens Dozenten- oder Adminrechte innerhalb des Kurses haben.
+## Eine Nutzer-News anlegen
+ `POST /users/{id}/news`
+
+ ```shell
+ curl --request POST \
+ --url https://example.com/users/<USER-ID>/news \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ '{"data": {"type": "news","attributes": {"title": "Neue News","comments-allowed": true,"publication-start": "2020-01-01T12:12:12+00:00","publication-end": "2021-01-01T12:12:12+00:00","content": "Eine neue News sieht das Tageslicht."}}}'
+ ```
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID des Users
+### Autorisierung
+ Der Nutzer muss mindestens User-Rechte haben.
+
+## Einen Kommentar anlegen
+ `POST /news/{id}/comments`
+
+ ```shell
+ curl --request POST \
+ --url https://example.com/news/<NEWS-ID>/comments \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_dozent:testing" | base64`" \
+ --data
+ '{"data": {"type": "comments","attributes": {"content": "Ein Kommentar wurde geupdatet"}}}'
+ ```
+ ### Autorisierung
+ Der Nutzer muss mindestens User-Rechte haben.
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID der News
+## Eine News ändern
+ `PATCH /news/{id}`
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID der News
+
+ Die Data-Felder beim Update einer News sind optional.
+
+ ```shell
+ curl --request PATCH \
+ --url https://example.com/news/<NEWS-ID> \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ '{"data": {"type": "news","attributes": {"title": "Aenderungen","comments-allowed": true,"publication-start": "2020-01-01T12:12:12+00:00","publication-end": "2021-01-01T12:12:12+00:00","content": "Eine News wurde geaendert."}}}'
+ ```
+### Autorisierung
+ Der Nutzer muss Inhaber der News sein oder die entsprechenden Root-Rechte
+ besitzen.
+## Eine News ansehen
+ `GET /news/{id}`
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID der News
+ ```shell
+ curl --request GET \
+ --url https://example.com/news/<NEWS-ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ ```
+### Autorisierung
+ Der Nutzer muss Inhaber der News sein oder die entsprechenden Range-Rechte
+ besitzen.
+ > Der Request liefert JSON ähnlich wie dieses:
+
+```json
+{
+ "data": {
+ "type": "news",
+ "id": "6a8be7e4859e9c781ecc47a2c3498435",
+ "attributes": {
+ "title": "A testing title",
+ "content": "Lorem ipsum dolor sit amet, consectetur adipisicing elit",
+ "mkdate": "2019-04-23T12:10:26+02:00",
+ "chdate": "2019-04-23T12:10:26+02:00",
+ "publication-start": "2019-04-23T12:10:26+02:00",
+ "publication-end": "2019-05-07T12:10:26+02:00",
+ "comments-allowed": true
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "users",
+ "id": "e7a0a84b161f3e8c09b4a0a2e8a58147"
+ },
+ "links": {
+ "related": "jsonapi.php/v1/users/e7a0a84b161f3e8c09b4a0a2e8a58147"
+ }
+ },
+ "ranges": {
+ "data": [
+
+ ],
+ "links": {
+ "self": "jsonapi.php/v1/news/6a8be7e4859e9c781ecc47a2c3498435/relationships/ranges"
+ }
+ }
+ },
+ "links": {
+ "self": "jsonapi.php/v1/news/6a8be7e4859e9c781ecc47a2c3498435"
+ }
+ }
+}
+```
+
+## Alle Kurs-News
+ `GET /courses/{id}/news`
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID des Kurses
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/course/<COURSE-ID>/news \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ ```
+### Autorisierung
+ Der Nutzer muss mindestens Teilnehmer des Kurses sein.
+
+ > Der Request liefert JSON ähnlich wie dieses:
+
+```json
+{
+ "meta": {
+ "page": {
+ "offset": 0,
+ "limit": 30,
+ "total": 4
+ }
+ },
+ "links": {
+ "first": "/stud35/plugins.php/argonautsplugin/courses/1b7d3834e42c1569947e0eab7b63ed19/news?page%5Boffset%5D=0&page%5Blimit%5D=30",
+ "last": "/stud35/plugins.php/argonautsplugin/courses/1b7d3834e42c1569947e0eab7b63ed19/news?page%5Boffset%5D=0&page%5Blimit%5D=30"
+ },
+ "data": [
+ {
+ "type": "news",
+ "id": "9dc34d7414e9d6c2789923649a64673e",
+ "attributes": {
+ "title": "Fakenews",
+ "content": "This is fakenews232",
+ "mkdate": "2018-06-20T10:40:43+02:00",
+ "chdate": "2018-06-20T10:57:37+02:00",
+ "publication-start": "2018-06-21T12:00:00+02:00",
+ "publication-end": "2066-12-09T22:00:00+01:00",
+ "comments-allowed": true
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "users",
+ "id": "76ed43ef286fb55cf9e41beadb484a9f"
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/users/76ed43ef286fb55cf9e41beadb484a9f"
+ }
+ },
+ "ranges": {
+ "data": [
+ {
+ "type": "courses",
+ "id": "1b7d3834e42c1569947e0eab7b63ed19"
+ }
+ ],
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/news/9dc34d7414e9d6c2789923649a64673e/relationships/ranges"
+ }
+ }
+ },
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/news/9dc34d7414e9d6c2789923649a64673e"
+ }
+ },
+ {
+ "type": "news",
+ "id": "0e8df7da383d7515c4dc081bfe889897",
+ "attributes": {
+ "title": "Fakenews",
+ "content": "This is fakenews232",
+ "mkdate": "2018-06-19T16:08:51+02:00",
+ "chdate": "2018-06-20T09:50:02+02:00",
+ "publication-start": "2018-06-19T16:08:51+02:00",
+ "publication-end": "2066-12-05T15:08:51+01:00",
+ "comments-allowed": true
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "users",
+ "id": "76ed43ef286fb55cf9e41beadb484a9f"
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/users/76ed43ef286fb55cf9e41beadb484a9f"
+ }
+ },
+ "ranges": {
+ "data": [
+ {
+ "type": "courses",
+ "id": "1b7d3834e42c1569947e0eab7b63ed19"
+ }
+ ],
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/news/0e8df7da383d7515c4dc081bfe889897/relationships/ranges"
+ }
+ }
+ },
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/news/0e8df7da383d7515c4dc081bfe889897"
+ }
+ },
+ {
+ "type": "news",
+ "id": "191ce64590a28b3e038b09e85ea53178",
+ "attributes": {
+ "title": "Fakenews",
+ "content": "This is fakenews",
+ "mkdate": "2018-05-08T11:42:11+02:00",
+ "chdate": "2018-05-08T11:42:11+02:00",
+ "publication-start": "2018-05-08T11:42:11+02:00",
+ "publication-end": "2066-09-19T20:24:22+01:00",
+ "comments-allowed": true
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "users",
+ "id": "76ed43ef286fb55cf9e41beadb484a9f"
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/users/76ed43ef286fb55cf9e41beadb484a9f"
+ }
+ },
+ "ranges": {
+ "data": [
+ {
+ "type": "courses",
+ "id": "1b7d3834e42c1569947e0eab7b63ed19"
+ }
+ ],
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/news/191ce64590a28b3e038b09e85ea53178/relationships/ranges"
+ }
+ }
+ },
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/news/191ce64590a28b3e038b09e85ea53178"
+ }
+ },
+ {
+ "type": "news",
+ "id": "a355b8d628d8656eb93dc527fe1209f3",
+ "attributes": {
+ "title": "Fakenews",
+ "content": "This is fakenews",
+ "mkdate": "2018-05-08T11:35:56+02:00",
+ "chdate": "2018-05-08T11:35:56+02:00",
+ "publication-start": "2018-05-08T11:35:55+02:00",
+ "publication-end": "2066-09-19T20:11:50+01:00",
+ "comments-allowed": true
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "users",
+ "id": "76ed43ef286fb55cf9e41beadb484a9f"
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/users/76ed43ef286fb55cf9e41beadb484a9f"
+ }
+ },
+ "ranges": {
+ "data": [
+ {
+ "type": "courses",
+ "id": "1b7d3834e42c1569947e0eab7b63ed19"
+ }
+ ],
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/news/a355b8d628d8656eb93dc527fe1209f3/relationships/ranges"
+ }
+ }
+ },
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/news/a355b8d628d8656eb93dc527fe1209f3"
+ }
+ }
+ ]
+}
+```
+
+
+## Alle Nutzer-News
+ `GET /users/{id}/news`
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID des Nutzers
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/user/<USER-ID>/news \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ ```
+### Autorisierung
+ Der Nutzer muss mindestens eingeloggt sein oder über Root-Rechte verfügen.
+
+ > Der Request liefert JSON ähnlich wie dieses:
+
+```json
+{
+ "data": {
+ "type": "news",
+ "id": "0e8df7da383d7515c4dc081bfe889897",
+ "attributes": {
+ "title": "Neue News",
+ "content": "Eine neue News sieht das Tageslicht.",
+ "mkdate": "2018-06-19T16:08:51+02:00",
+ "chdate": "2018-08-15T14:22:38+02:00",
+ "publication-start": "2018-06-19T16:08:51+02:00",
+ "publication-end": "2066-12-05T15:08:51+01:00",
+ "comments-allowed": true
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "users",
+ "id": "76ed43ef286fb55cf9e41beadb484a9f"
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/users/76ed43ef286fb55cf9e41beadb484a9f"
+ }
+ },
+ "ranges": {
+ "data": [
+ {
+ "type": "users",
+ "id": "1b7d3834e42c1569947e0eab7b63ed19"
+ }
+ ],
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/news/0e8df7da383d7515c4dc081bfe889897/relationships/ranges"
+ }
+ }
+ },
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/news/0e8df7da383d7515c4dc081bfe889897"
+ }
+ }
+}
+```
+
+## Alle News-Kommentare
+ GET /news/{id}/comments
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID einer News
+
+## Alle globalen News
+ `GET /studip/news`
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/user/studip/news \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ ```
+
+### Autorisierung
+
+ Der Nutzer muss mindestens eingeloggt sein oder über Root-Rechte verfügen.
+
+> Der Request liefert JSON ähnlich wie dieses:
+
+```json
+{
+ "data": {
+ "type": "news",
+ "id": "0e8df7da383d7515c4dc081bfe889897",
+ "attributes": {
+ "title": "Globale News",
+ "content": "Eine neue News sieht das Tageslicht.",
+ "mkdate": "2018-06-19T16:08:51+02:00",
+ "chdate": "2018-08-15T14:22:38+02:00",
+ "publication-start": "2018-06-19T16:08:51+02:00",
+ "publication-end": "2066-12-05T15:08:51+01:00",
+ "comments-allowed": true
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "users",
+ "id": "76ed43ef286fb55cf9e41beadb484a9f"
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/users/76ed43ef286fb55cf9e41beadb484a9f"
+ }
+ },
+ "ranges": {
+ "data": [
+ {
+ "type": "global",
+ "id": "studip"
+ }
+ ],
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/news/0e8df7da383d7515c4dc081bfe889897/relationships/ranges"
+ }
+ }
+ },
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/news/0e8df7da383d7515c4dc081bfe889897"
+ }
+ }
+}
+```
+
+
+## Alle News des aktuell eingeloggten Nutzers abrufen
+ `GET /news`
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/news \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ ```
+
+### Autorisierung
+
+ Der Nutzer muss mindestens eingeloggt sein oder über Root-Rechte verfügen.
+
+> Der Request liefert JSON ähnlich wie dieses:
+
+```json
+{
+ "data": {
+ "type": "news",
+ "id": "0e8df7da383d7515c4dc081bfe889897",
+ "attributes": {
+ "title": "Neue News",
+ "content": "Eine neue News sieht das Tageslicht.",
+ "mkdate": "2018-06-19T16:08:51+02:00",
+ "chdate": "2018-08-15T14:22:38+02:00",
+ "publication-start": "2018-06-19T16:08:51+02:00",
+ "publication-end": "2066-12-05T15:08:51+01:00",
+ "comments-allowed": true
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "users",
+ "id": "76ed43ef286fb55cf9e41beadb484a9f"
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/users/76ed43ef286fb55cf9e41beadb484a9f"
+ }
+ },
+ "ranges": {
+ "data": [
+ {
+ "type": "users",
+ "id": "1b7d3834e42c1569947e0eab7b63ed19"
+ }
+ ],
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/news/0e8df7da383d7515c4dc081bfe889897/relationships/ranges"
+ }
+ }
+ },
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/news/0e8df7da383d7515c4dc081bfe889897"
+ }
+ }
+}
+```
+
+
+## Eine News löschen
+ `DELETE /news/{id}`
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID der News
+
+### Authorisierung
+
+Diese Route kann nur vom Nutzer der betreffenden Nachrichten genutzt werden.
+
+ ```shell
+ curl --request DELETE \
+ --url https://example.com/news/<NEWS-ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ ```
+ ### Autorisierung
+
+ Der Nutzer muss mindestens eingeloggt sein oder über Root-Rechte verfügen.
+
+## Einen Kommentar löschen
+ `DELETE /comments/{id}`
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID eines Kommentars
+
+ ```shell
+ curl --request DELETE \
+ --url https://example.com/comments/studip/news \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ ```
+## Alle News-Ranges
+ `GET /news/{id}/relationships/ranges`
+
+ see http://jsonapi.org/format/#fetching-relationships
+
+## News-Ranges setzen
+ `PATCH /news/{id}/relationships/ranges`
+
+ see http://jsonapi.org/format/#crud-updating-to-many-relationships
+
+## News-Ranges hinzufügen
+ `POST /news/{id}/relationships/ranges`
+
+ see http://jsonapi.org/format/#crud-updating-to-many-relationships
+
+## News-Ranges löschen
+ `DELETE /news/{id}/relationships/ranges`
+
+ see http://jsonapi.org/format/#crud-updating-to-many-relationships
diff --git a/docs/docs/jsonapi/planer.md b/docs/docs/jsonapi/planer.md
new file mode 100644
index 0000000..043ede0
--- /dev/null
+++ b/docs/docs/jsonapi/planer.md
@@ -0,0 +1,294 @@
+---
+title: Planer
+---
+
+
+In diese Kategorie fällt alles, was mit dem Stundenplan oder dem
+Terminkalender zu tun hat. Dabei ist der Stundenplan ein
+semesterabhängiger Wochenplan, der wöchentlich wiederkehrende Termine
+enthält, die entweder selbst eingetragen wurden oder aufgrund von
+besuchten Veranstaltungen dort hinein gelangen.
+
+## Schemata
+
+### Schema "calendar-events"
+
+Ressourcen dieses Typs sind selbst in den Terminkalender eingetragene Termine.
+
+
+#### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+title | der Titel des Eintrags
+description | die Beschreibung des Eintrags
+start | der Beginn des Eintrags (ISO 8601)
+end | das Ende des Eintrag (ISO 8601)
+categories | die Kategorie des Eintrags (als String)
+location | der Ort des Eintrags
+mkdate | das Erstellungsdatum des Eintrags
+chdate | das Datum der letzten Änderung des Eintrags
+recurrence | Informationen über Wiederholungen des Eintrags
+
+#### Relationen
+
+Relation | Beschreibung
+-------- | ------------
+owner | der Nutzer, der den Eintrag erstellt hat
+
+### Schema "course-events"
+
+Ressourcen dieses Typs repräsentieren einmalige Termine von belegten Veranstaltungen.
+
+#### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+title | der Titel des Eintrags
+description | die Beschreibung des Eintrags
+start | der Beginn des Eintrags (ISO 8601)
+end | das Ende des Eintrag (ISO 8601)
+categories | die Kategorie des Eintrags (als String)
+location | der Ort des Eintrags
+mkdate | das Erstellungsdatum des Eintrags
+chdate | das Datum der letzten Änderung des Eintrags
+recurrence | Informationen über Wiederholungen des Eintrags
+
+
+#### Relationen
+
+Relation | Beschreibung
+-------- | ------------
+owner | die **Veranstaltung**, zu der der Eintrag gehört
+
+
+### Schema "schedule-entries"
+
+Ressourcen dieses Typs stellen Einträge in den Stundenplan dar, die
+ein Nutzer selbst eingetragen hat.
+
+#### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+title | der Titel des Eintrags
+description | die Beschreibung des Eintrags
+start | die Uhrzeit des Beginns des Eintrags ("hh:mm")
+end | die Uhrzeit des Endes des Eintrags ("hh:mm")
+weekday | der Wochentag des Eintrags (0-6)
+color | die Farbe des Eintrags ("#rrggbb")
+
+#### Relationen
+
+Relation | Beschreibung
+-------- | ------------
+owner | der Nutzer, der den Eintrag erstellt hat
+
+
+### Schema "seminar-cycle-dates"
+
+Ressourcen dieses Typs stellen Einträge in den Stundenplan dar, die
+sich aus den regelmäßigen Terminen einer Veranstaltung zusammensetzen.
+
+#### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+title | der Titel des Eintrags
+description | die Beschreibung des Eintrags
+start | die Uhrzeit des Beginns des Eintrags ("hh:mm")
+end | die Uhrzeit des Endes des Eintrags ("hh:mm")
+weekday | der Wochentag des Eintrags (0-6)
+recurrence | Informationen über Wiederholungen des Eintrags
+locations | alle Orte, an denen dieser Eintrag stattfindet
+
+#### Relationen
+
+Relation | Beschreibung
+-------- | ------------
+owner | die **Veranstaltung**, zu der der Eintrag gehört
+
+## Alle Kalendereinträge auslesen
+
+```shell
+curl --request GET \
+ --url https://example.com/users/<ID>/events \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route kann der Kalender des Nutzer abgefragt werden. Ohne
+weitere Parameter werden alle Einträge der nächsten zwei Wochen zurück
+geliefert.
+
+### HTTP Request
+
+`GET /users/{id}/events`
+
+Parameter | Beschreibung
+--------- | ------------
+id | die ID des Nutzers
+
+### URL-Parameter
+
+Parameter | Beschreibung
+--------- | ------------
+filter[timestamp] | Startzeitpunkt der gelieferten Kalendereinträge (als Sekunden seit dem 01.01.1970)
+
+Wenn "filter[timestamp]" nicht gesetzt ist, werden alle
+Kalendereinträge der nächsten zwei Wochen zurück geliefert.
+
+Mittels "filter[timestamp]" kann dieser Startzeitpunkt verändert
+werden. Es werden jedoch immer Kalendereinträge der nächsten zwei
+Wochen ausgeliefert.
+
+### Autorisierung
+
+Jeder Nutzer darf diese Route für sich selbst verwenden. Andere Nutzer
+haben nur Zugriff auf ihre eigenen Kalender.
+
+
+## Alle Kalendereinträge (iCalendar)
+
+:::danger
+Diese Route ist keine JSON-API-konforme Route.
+:::
+
+```shell
+curl --request GET \
+ --url https://example.com/users/<ID>/events.ics \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route kann der Kalender des Nutzer abgefragt werden. Die
+Daten werden im iCalendar-Datenformat ausgeliefert. Es werden **alle**
+Kalendereinträge zurück gegeben.
+
+### HTTP Request
+
+`GET /users/{id}/events.ics`
+
+Parameter | Beschreibung
+--------- | ------------
+id | die ID des Nutzers
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Jeder Nutzer darf diese Route für sich selbst verwenden. Andere Nutzer
+haben nur Zugriff auf ihre eigenen Kalender.
+
+
+## Alle Termine einer Veranstaltung
+
+```shell
+curl --request GET \
+ --url https://example.com/courses/<ID>/events \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route können alle Termine einer Veranstaltung abgefragt werden.
+
+### HTTP Request
+
+`GET /courses/{id}/events`
+
+Parameter | Beschreibung
+--------- | ------------
+id | die ID der Veranstaltung
+
+### URL-Parameter
+
+Parameter | Default | Beschreibung
+--------- | ------- | ------------
+page[offset] | 0 | der Offset (siehe Paginierung)
+page[limit] | 30 | das Limit (siehe Paginierung)
+
+
+### Autorisierung
+
+Die Termine einer Veranstaltung sind für alle Teilnehmenden sichtbar.
+
+
+## Stundenplan auslesen
+```shell
+curl --request GET \
+ --url https://example.com/users/<ID>/schedule \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route kann man seinen Stundenplan abfragen. Wird kein
+filter-Parameter angegeben, wird der Stundenplan des aktuellen
+Semesters ausgeliefert.
+
+### HTTP Request
+
+`GET /users/{id}/schedule`
+
+Parameter | Beschreibung
+--------- | ------------
+id | die ID des Nutzers
+
+### URL-Parameter
+
+Parameter | Default | Beschreibung
+--------- | ------- | ------------
+filter[timestamp] | Beginn des aktuellen Semesters | Startzeitpunkt des gewünschten Semesters (in Sekunden seit 01.01.1970)
+
+### Autorisierung
+
+Nur der eigene Stundenplan kann ausgelesen werden.
+
+
+## Eigene Stundenplaneinträge
+```shell
+curl --request GET \
+ --url https://example.com/schedule-entries/<ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route kann man einen einzelnen, selbst verfassten Eintrag
+in den Stundenplan auslesen.
+
+### HTTP Request
+
+`GET /schedule-entries/{id}`
+
+Parameter | Beschreibung
+--------- | ------------
+id | die ID des Eintrags
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Nur der Nutzer, der den Eintrag verfasst hat, darf den Eintrag auch auslesen.
+
+## Regelmäßige Veranstaltungstermine auslesen
+```shell
+curl --request GET \
+ --url https://example.com/seminar-cycle-dates/<ID> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Beschreibung
+
+### HTTP Request
+
+`GET /seminar-cycle-dates/{id}`
+
+Parameter | Beschreibung
+--------- | ------------
+id | die ID des Termins
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Alle Teilnehmenden einer Veranstaltung können den Termin sehen.
diff --git a/docs/docs/jsonapi/resources.md b/docs/docs/jsonapi/resources.md
new file mode 100644
index 0000000..cc77fff
--- /dev/null
+++ b/docs/docs/jsonapi/resources.md
@@ -0,0 +1,123 @@
+---
+title: Räume/Gebäude
+---
+
+
+In dieser Kategorie ist alles versammelt, was mit Ressourcenverwaltung zu tun hat.
+
+
+## Schema "resources-objects"
+
+Alle Ressourcenobjekte der Ressourcenverwaltung werden mit diesem Schema repräsentiert.
+
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+name | der Name der Ressource
+description | die Beschreibung der Ressource
+is-room | Handelt es sich bei dieser Ressource um einen Raum?
+multiple-assign | Darf diese Ressource zeitgleich mehrfach belegt werden?
+requestable | Kann man zu dieser Ressource eine Raumanfrage stellen?
+lockable | Ist diese Ressource betroffen von einer globalen Sperrzeit?
+mkdate | Erstellungsdatum
+chdate | Änderungsdatum
+
+### Relationen
+
+Relation | Beschreibung
+-------- | ------------
+category | Kategorie der Ressource
+
+
+## Schema "resources-categories"
+
+Dieses Schema beschreibt Ressourcenarten.
+
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+name | der Name der Art
+description | die Beschreibung der Art
+system |
+is-room | Handelt es sich bei dieser Art um einen Raum?
+icon | Nummer des zu verwendenden Icons
+
+### Relationen
+
+keine Relationen
+
+
+## Schema "resources-assign-events"
+
+Alle Ressourcenbelegungen werden mit diesem Schema abgebildet.
+
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+repeat-mode | in welchem Abstand und in welcher Frequenz wird diese Ressourcenbelegung ausgeführt
+start | das Datum des Beginns der Belegung
+end | das Datum des Endes der Belegung
+owner-free-text | Freitextangabe für den Besitzer dieser Belegung
+
+### Relationen
+
+Relation | Beschreibung
+-------- | ------------
+owner | (optional) der Besitzer der Belegung
+resources-object | die belegte Ressource
+
+
+## Alle Ressourcen
+```shell
+curl --request GET \
+ --url https://example.com/resources-objects \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Diese Route liefert alle Ressourcenobjekte.
+
+### HTTP Request
+
+`GET /resources-objects`
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Jeder eingeloggte Nutzer kann die Liste der Ressourcenobjekte sehen.
+
+
+## Alle Belegungen einer Ressource
+```shell
+curl --request GET \
+ --url https://example.com/resources-objects/<ID>/assignments \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Mit dieser Route können die Belegungen einer Ressource abgefragt werden.
+
+### HTTP Request
+
+`GET /resources-objects/{id}/assignments`
+
+Parameter | Beschreibung
+--------- | ------------
+id | die ID des Ressourcenobjekts
+
+### URL-Parameter
+
+Parameter | Default | Beschreibung
+--------- | ------- | ------------
+filter[start] | (heute) | optional; Zeitpunkt (in Sekunden seit 1.1.1970), ab dem die Belegungen angezeigt werden sollen
+filter[end] | (morgen) | optional; Zeitpunkt (in Sekunden seit 1.1.1970), bis zu dem die Belegungen angezeigt werden sollen
+
+Die Parameter "filter[start]" und "filter[end]" müssen als Integer angegeben werden (Sekunden seit 1.1.1970 00:00:00 UTC). Werden sie nicht angegeben, werden die Belegungen des heutigen Tages angezeigt.
+
+### Autorisierung
+
+Jeder eingeloggte Nutzer kann die Liste der Ressourcenbelegungen sehen.
diff --git a/docs/docs/jsonapi/routen.md b/docs/docs/jsonapi/routen.md
new file mode 100644
index 0000000..1771c34
--- /dev/null
+++ b/docs/docs/jsonapi/routen.md
@@ -0,0 +1,299 @@
+---
+title: Routen
+slug: /jsonapi/routen
+sidebar_label: Routen
+---
+
+Diese Dokumentation befasst sich mit der Entwicklung von JSON:API-Routen.
+
+Die Stud.IP JSON:API ist zu erreichen unter der URI:
+
+`https://<meine.studip.installation.de>/<eventuell-mit-pfad>/jsonapi.php/v1/<routen>`
+
+Für den Developer-Server also zum Beispiel unter:
+
+`https://develop.studip.de/studip/jsonapi.php/v1/semesters`
+
+
+### Was ist die Stud.IP JSON:API?
+Die Stud.IP JSON:API implementiert eine externe Schnittstelle zum Zugriff auf Stud.IP-Datenstrukturen und entspricht der JSON:API-Spezifikation (https://jsonapi.org/). Zur Verständnis empfiehlt es sich, diese Spezifikation zu lesen.
+
+Geht ein JSON:API-Request ein, werden nacheinander die folgenden Schritte durchlaufen:
+
+* Routen-Zuordnung: Welcher Code wird für welche URI und welches HTTP-Verb ausgeführt?
+* Routen-Handler: Liefert eine JSON:API-konforme Antwort. Oft werden ein oder mehrere Stud.IP-Objekte zurückgegeben.
+* Schema-Zuordnung: Welche Schemaklasse kann Objekte einer bestimmten Stud.IP-Klasse in JSON umwandeln?
+* Schemas: Definiert die Abbildung eines Stud.IP-Objekts in JSON.
+
+
+![image](../assets/3a528f8f2de835a0ba5c4e342929179a/image.png)
+JSON:API-Ablauf
+
+### Routen-Zuordnung
+
+In der Datei `/lib/classes/JsonApi/RouteMap.php` werden URIs auf zuständigen Code abgebildet. Dabei beginnen alle URIs immer mit `<STUDIP-URI>/jsonapi.php/v1/`. Sobald ein Request an solche URIs geht, wird mithilfe der `RouteMap` der entsprechende Code, der Routen-Handler, herausgesucht und aufgerufen.
+
+Routen können erfordern, dass Nutzer angemeldet sind: In diesem Fall werden die Routen in der Methode `RouteMap#authenticatedRoutes` definiert. Ist keine Nutzeranmeldung erforderlich, werden die Routen in `RouteMap#unauthenticatedRoutes` definiert.
+
+Da `Slim` für das Routing verwendet wird, lohnt sich ein Blick in die entsprechende [Doku.](https://www.slimframework.com/docs/v3/objects/router.html)
+
+Ausschnitt aus der der Datei `RouteMap.php`:
+
+```php
+namespace JsonApi;
+
+class RouteMap
+{
+ public function authenticatedRoutes()
+ {
+ //
+ $this->app->get('/blubber-comments', Routes\Blubber\CommentsIndex::class);
+ $this->app->get('/blubber-comments/{id}', Routes\Blubber\CommentsShow::class);
+ $this->app->patch('/blubber-comments/{id}', Routes\Blubber\CommentsUpdate::class);
+ $this->app->delete('/blubber-comments/{id}', Routes\Blubber\CommentsDelete::class);
+ //
+ }
+ //
+}
+```
+
+
+### Routen-Handler
+
+Routen-Handler sind Unterklassen von `JsonApi\JsonApiController` und implementieren die magische Methode `__invoke`. Routen-Handler verhalten sich JSON:API-konform und bedienen sich dabei insbesondere der geerbeten Methoden:
+
+* `getContentResponse`
+* `getPaginatedContentResponse`
+* `getCreatedResponse`
+* `getCodeResponse`
+
+Die wichtigsten Methoden sind dabei `getContentResponse` und `getPaginatedContentResponse`, da sie verwendet werden, um Stud.IP-Objekte zurückzugeben. Der Unterschiede wird schon im Namen deutlich. Die paginierte Variante funktioniert nur mit Listen von Stud.IP-Objekten.
+
+Beide Methoden werden verwendet, wenn in der JSON:API Stud.IP-Datenstrukturen ausgelesen werden sollen, wenn also ein `GET`-Request an die Stud.IP JSON:API gerichtet wurde.
+
+Dazu übergibt man lediglich das Stud.IP-Objekt an diese Methode und ist fertig:
+
+```php
+ // in der RouteMap
+ $this->app->get('/blubber-threads/{id}', Routes\Blubber\ThreadsShow::class);
+```
+
+```php
+class ThreadsShow extends JsonApiController
+{
+ public function __invoke(Request $request, Response $response, $args)
+ {
+ if (!$resource = \BlubberThread::find($args['id'])) {
+ throw new RecordNotFoundException();
+ }
+
+ if (!Authority::canShowBlubberThread($this->getUser($request), $resource)) {
+ throw new AuthorizationFailedException();
+ }
+
+ return $this->getContentResponse($resource);
+ }
+}
+```
+
+Hier sieht man den generellen Aufruf der Methode `getContentResponse`.
+
+* Der Routen-Handler `ThreadsShow` ist eine Unterklasse von `JsonApi\JsonApiController`.
+* Der Routen-Handler implementiert die magische Methode `__invoke`.
+* Hier kommt der typische Dreisatz: Auslesen, Authorisieren, Zurückgeben.
+* Um den `BlubberThread` auslesen zu können, entnehmen wir der URI den Parameter `id`. Dieser war in der RouteMap definiert worden.
+* Nun überprüfen wir, ob der eingeloggte Nutzer diese Daten lesen darf. Dafür verwenden wir die Methode `JsonApiController#getUser`.
+* Zum Schluß übergeben wir den ausgelesenen `BlubberThread` an `getContentResponse` und das Ergebnis ist dann auch das Ergebnis des Requests.
+
+### Schema-Zuordnung
+
+Wie kann die Stud.IP-JSON:API wissen, wie aus einem Stud.IP-`BlubberThread`-Objekt spezifikationskonformes JSON wird?
+
+Dafür ist zunächst die Schema-Zuordnung wichtig. Diese befindet sich in der Datei `/lib/classes/JsonApi/SchemaMap.php`. Und darin werden Stud.IP-Klassen auf Schema-Klassen abgebildet:
+
+```php
+\BlubberThread::class => \JsonApi\Schemas\BlubberThread::class,
+```
+
+Wird also mithilfe von beispielsweise `getContentResponse` ein `BlubberThread`-Objekt geliefert, wird die Schema-Klasse `JsonApi\Schemas\BlubberThread` für die Umwandlung verwendet.
+
+### Schemaklassen
+
+Schemaklassen machen aus einem Stud.IP-Objekt eine JSON:API-konforme Repräsentation. Die `User`-Schemaklasse macht zum Beispiel aus diesem Objekt:
+
+```php
+$me = \User::findCurrent();
+```
+
+diese Darstellung, die JSON:API-konforme ist:
+
+```javascript
+{
+ "data": {
+ "type": "users",
+ "id": "205f3efb7997a0fc9755da2b535038da",
+ "attributes": {
+ "username": "test_dozent",
+ "formatted-name": "Testaccount Dozent",
+ "family-name": "Dozent",
+ "given-name": "Testaccount",
+ "name-prefix": "",
+ "name-suffix": "",
+ "permission": "dozent",
+ "email": "dozent@studip.de",
+ "phone": null,
+ "homepage": null,
+ "address": null
+ },
+ "relationships": {
+ "activitystream": {
+ "links": {
+ "related": "jsonapi.php/v1/users/205f3efb7997a0fc9755da2b535038da/activitystream"
+ }
+ },
+ }
+}
+```
+
+Wenn man die Schemaklassen in aller Ausführlichkeit verstehen möchte, sollte man zuvor den entsprechenden Teil der JSON:API-Spezifikation gelesen haben: https://jsonapi.org/format/#document-structure
+
+Die Schemaklassen bieten alle Möglichkeiten, die in der Spezifikation vorgestellt werden. Am wichtigsten sind sicherlich aber die ID, der Type, die Attribute und Relationships eines `Resource Object`s.
+
+Zunächst ein Beispiel: Diese Schemaklasse beschreibt die Umwandlung von Stud.IPs `Semester`-Objekten in eine spezifikationskonforme JSON-Form.
+
+```php
+<?php
+
+namespace JsonApi\Schemas;
+
+class Semester extends SchemaProvider
+{
+ const TYPE = 'semesters';
+
+ // [A]: Type
+ protected $resourceType = self::TYPE;
+
+ // [B]: ID
+ public function getId($semester)
+ {
+ return $semester->id;
+ }
+
+ // [C]: Attributes
+ public function getAttributes($semester)
+ {
+ return [
+ 'title' => (string) $semester->name,
+ 'description' => (string) $semester->description,
+ 'start' => date('c', $semester->beginn),
+ 'end' => date('c', $semester->ende),
+ ];
+ }
+}
+```
+
+#### ID und Type
+Laut [Spezifikation](https://jsonapi.org/format/#document-resource-object-identification) benötigt jedes `Resource Object` eine ID und einen Type. Im Beispiel oben werden an Stelle A der Type und an Stelle B die ID definiert. Für alle Stud.IP-JSON:API-Types gilt:
+
+* Der Type steht immer im Plural.
+* Der Type muss in `kebap-case` geschrieben sein.
+
+Die ID wird über die überschriebene Methode `getId` festgelegt und muss einen String zurückliefern.
+
+#### Attribute
+Die [Spezifikation](https://jsonapi.org/format/#document-resource-object-attributes) ist bezüglich der Attribute von `Resource Objects` sehr klar. In der Stud.IP JSON:API werden sie definiert, indem man die Methode `getAttributes` überschreibt.
+
+* Rückgabewert muss ein PHP-Array sein.
+* Schlüssel und Werte müssen UTF-8 kodiert sind.
+* Erlaubte Zeichen für Schlüssel werden in der [Spezifikation](https://jsonapi.org/format/#document-member-names) definiert.
+* Folgende Schlüssel können nicht gewählt werden: `type`, `id`, `data`.
+* Die in Stud.IP-SORM häufig direkt verwendeten Fremdschlüssel `<irgendwas>_id` sollten in aller Regel keine Attribute sondern Relationen sein.
+* Etwaige menschenlesbare Versionen von Attributen müssen den Zusatz `-readable` am Attributnamen erhalten.
+
+#### Relationships
+Die [Relationships](https://jsonapi.org/format/#document-resource-object-relationships) sind ein sehr mächtiges Merkmal der JSON:API-Spezifikation. Es empfiehlt sich sehr, die entsprechenden Kapitel zu lesen, um die verschiedene Termini zu kennen.
+
+Letztendlich muss auch hier wieder die Methode `getRelationships` überschrieben werden, die ein Array von Relationships liefert. Wesentlich für eine Relation sind sicherlich:
+
+* Die Relationship möchte Daten liefern: `data`
+* Die Relationship möchte einen Link zur Relation selbst liefern: `links[self]`
+* Die Relationship möchte einen Link auf das verknüpfte Objekt liefern: `links[related]`
+
+Eine Relationship mit diesen drei Merkmalen gleichzeitig, sieht im Beispiel so aus:
+```php
+<?php
+
+namespace JsonApi\Schemas;
+
+class BlubberThread extends SchemaProvider
+{
+ //
+ public function getRelationships($resource, $isPrimary, array $includeList)
+ {
+ $relationships = [];
+
+ //
+
+ $course = \Course::find($resource['context_id']);
+ $relationships[self::REL_CONTEXT] = [
+ self::SHOW_SELF => true,
+ self::LINKS => [
+ Link::RELATED => new Link('/courses/'.$course->id)
+ ],
+ self::DATA => $course
+ ];
+
+ //
+
+ return $relationships;
+ }
+}
+```
+
+Die `context`-Relationship eines BlubberThreads möchte:
+* Daten liefern und legt diese unter dem Schlüssel `self::DATA` in der Relationship ab.
+* einen Link zur Relationship selbst liefern und ergänzt daher: `self::SHOW_SELF => true`
+* einen Link auf das verknüpfte Objekt liefern und setzt daher einen entsprechenden Eintrag im `self::LINKS`-Array.
+
+#### Was ist mit Plugins?
+
+Plugins dürfen ebenfalls Routen und Schemata registrieren. Dazu muss
+ein Plugin lediglich das Plugin-Interface `JsonApi\Contracts\JsonApiPlugin` implementieren.
+
+Ein Beispiel:
+
+```php
+<?php
+
+use JsonApi\Contracts\JsonApiPlugin;
+
+class MyPlugin extends StudIPPlugin implements StandardPlugin, JsonApiPlugin
+{
+ //
+
+ public function registerAuthenticatedRoutes(\Slim\App $app)
+ {
+ $app->get('/whiteboards', WhiteboardsIndex::class);
+ $app->get('/whiteboards/{id}', WhiteboardsShow::class);
+ }
+
+ public function registerUnauthenticatedRoutes(\Slim\App $app)
+ {
+ $app->get('/whiteboard-colors', WhiteboardColorsIndex::class);
+ }
+
+ public function registerSchema()
+ {
+ return [
+ Whiteboard::class => WhiteboardSchema::class,
+ WhiteboardColor::class => WhiteboardColorSchema::class
+ ];
+ }
+}
+```
+
+Die darin angegebenen Routen und Schemata werden dann wie oben beschrieben implementiert.
+
+Ein Beispiel zur Einbindung findet sich hier:
+
+https://gitlab.studip.de/marcus/studip-plugin-jsonapi-example
diff --git a/docs/docs/jsonapi/semesters.md b/docs/docs/jsonapi/semesters.md
new file mode 100644
index 0000000..d882f7e
--- /dev/null
+++ b/docs/docs/jsonapi/semesters.md
@@ -0,0 +1,40 @@
+---
+title: Semester
+---
+
+Semester geben einen bestimmten Studien-Zeitraum in Stud.IP an.
+Sie dienen auch als Filter für Veranstaltungen und Suchen.
+
+## Schema "semesters"
+
+Neben dem Titel und der Beschreibung beinhalten Semester Meta-Daten über Start- und End-Zeitpunkt des Semesters
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+title | Name des Semesters
+description | Weitere Angaben zum Semester
+start | Startzeitpunkt
+end | Endzeitpunkt
+
+### Relationen
+
+keine
+
+## Alle Semester
+ GET /semesters
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/semesters \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ ```
+
+## Ein Semester
+ GET /semesters/{id}
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/semesters/<semester-id> \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ ```
diff --git a/docs/docs/jsonapi/start.md b/docs/docs/jsonapi/start.md
new file mode 100644
index 0000000..3489327
--- /dev/null
+++ b/docs/docs/jsonapi/start.md
@@ -0,0 +1,87 @@
+---
+title: Stud.IP JSON:API
+slug: /jsonapi/
+sidebar_label: Einführung
+---
+
+Willkommen bei der Dokumentation der Stud.IP-JSON:API! Mit dieser API
+kann auf viele Daten einer Stud.IP-Installation zugegriffen werden.
+Die API verhält sich konform zur <a rel="noopener noreferrer" href="http://jsonapi.org/">JSON:API-Spezifikation</a>.
+
+# Authentifizierung
+
+Stud.IP JSON:API verwendet drei verschiedene Verfahren um Nutzer
+zu authentifizieren:
+
+* HTTP Basic access authentication
+* Stud.IP-Session-Cookies
+* [OAuth2](../functions/oauth2)
+
+Für HTTP-Basic-Access-Authentication benötigt man die Zugangsdaten, die auch
+für ein „normales“ Login verwendet werden.
+
+# Paginierung
+
+Viele Routen der Stud.IP JSON:API liefern ihre Ergebnisse seitenweise.
+Die zu betrachtende Seite und die Anzahl der Einträge von Seiten
+können durch URL-Parameter beeinflusst werden.
+
+Routen, die ihre Ergebnisse seitenweise liefern, enthalten
+entsprechende `meta` und `links` in ihren Antworten:
+
+```json title="GET jsonapi.php/v1/courses"
+{
+ "meta": {
+ "page": {
+ "offset": 0,
+ "limit": 30,
+ "total": 347
+ }
+ },
+ "links": {
+ "first": "/studip/jsonapi.php/v1/courses?page%5Boffset%5D=0&page%5Blimit%5D=30",
+ "last": "/studip/jsonapi.php/v1/courses?page%5Boffset%5D=300&page%5Blimit%5D=30",
+ "next": "/studip/jsonapi.php/v1/courses?page%5Boffset%5D=30&page%5Blimit%5D=30"
+ },
+ "data": [
+ "[...]"
+ ]
+}
+```
+
+In diesem Fall wurden alle Veranstaltungen abgefragt, die Route
+liefert jedoch nur die erste Seite mit 30 der 347 Einträge zurück.
+Unterhalb von `links` werden auf die URLs der ersten, letzten und
+nächsten Seite verwiesen. In jedem Fall enthalten diese URLs die
+URL-Parameter `page[offset]` und `page[limit]`.
+
+Die Gesamtheit aller Ergebnisse wird auf mehrere Seiten verteilt und
+man erhält jeweils nur einen Ausschnitt. Dieser Ausschnitt kann durch
+diese URL-Parameter beeinflusst werden
+
+Page-Parameter | Beschreibung
+-------------- | ------------
+page[offset] | der Paginierungsoffset
+page[limit] | das Paginierungslimit
+
+Der `page`-Parameter wird der JSON:API-Spezifikation entsprechend verwendet.
+
+```json title="GET jsonapi.php/v1/courses?page[offset]=7&page[limit]=17"
+{
+ "meta": {
+ "page": {
+ "offset": 7,
+ "limit": 17,
+ "total": 347
+ }
+ },
+ "links": {
+ "first": "/studip/jsonapi.php/v1/courses?page%5Boffset%5D=0&page%5Blimit%5D=17",
+ "last": "/studip/jsonapi.php/v1/courses?page%5Boffset%5D=323&page%5Blimit%5D=17",
+ "next": "/studip/jsonapi.php/v1/courses?page%5Boffset%5D=24&page%5Blimit%5D=17"
+ },
+ "data": [
+ "[...]"
+ ]
+}
+```
diff --git a/docs/docs/jsonapi/studip.md b/docs/docs/jsonapi/studip.md
new file mode 100644
index 0000000..b3b2262
--- /dev/null
+++ b/docs/docs/jsonapi/studip.md
@@ -0,0 +1,48 @@
+---
+title: Stud.IP-System
+---
+
+Alle Routen, die mit dem Stud.IP-System an sich zu tun haben, sind in dieser Kategorie versammelt.
+
+## Schemata
+
+### Schema "studip-properties"
+
+Ausgewählte Konfigurationseinstellungen und Merkmale der Stud.IP-Installation werden mit diesem Schema abgebildet.
+
+### ID
+
+Die ID der Einstellung ist kein MD5-Hash sondern ein festes Kürzel für eine Einstellung/ein Merkmal.
+
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+description | eine Beschreibung der Einstellung/des Merkmals
+value | der Wert der Einstellung/des Merkmals
+
+### Relationen
+
+keine Relationen vorhanden
+
+## Alle Stud.IP-Properties auslesen
+```shell
+curl --request GET \
+ --url https://example.com/studip/properties \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Liefert alle Stud.IP-Einstellungen/Merkmale
+
+### HTTP Request
+
+`GET /studip/properties`
+
+
+### URL-Parameter
+
+keine URL-Parameter
+
+### Autorisierung
+
+Jeder eingeloggte Nutzer darf diese Route aufrufen.
diff --git a/docs/docs/jsonapi/users.md b/docs/docs/jsonapi/users.md
new file mode 100644
index 0000000..557a283
--- /dev/null
+++ b/docs/docs/jsonapi/users.md
@@ -0,0 +1,344 @@
+---
+title: Nutzer*innen
+---
+
+Nutzer*innen (`users`) von Stud.IP-Installationen können mit den folgenden Routen
+abgefragt werden.
+
+## Schema
+
+Alle Nutzer*innen werden in Stud.IP mit diesem Schema abgebildet. Die `id`
+entspricht der in Stud.IP verwendeten `user_id`. Der Typ ist `users`.
+
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+username | der `username` wird beim Login-Vorgang verwendet
+formatted-name | der formatierte Echtname
+family-name | der Nachname
+given-name | der Vorname
+name-prefix | evtl. vorangestellte Titel
+name-suffix | evtl. nachgestellte Titel
+permission | die globale Berechtigungsstufe
+email | die E-Mail-Adresse
+phone | die Telefonnummer
+homepage | die URL der Homepage
+address | die private Adresse
+
+Die Berechtigungsstufe kann eine der folgenden sein: `root`, `admin`,
+`dozent`, `tutor`, `autor`
+
+Die Sichtbarkeit der Attribute `phone`, `homepage`, `address` folgt
+den Sichtbarkeitseinstellungen, die Nutzer*innen vorgenommen haben.
+
+### Relationen
+
+:::info
+Nicht alle Relationen sind für alle Betrachtenden zugänglich.
+:::
+
+Relation | Beschreibung
+-------- | ------------
+activitystream | ein Link zum `activity stream`
+blubber-postings | die Blubber
+contacts | die Kontakte
+courses | die Veranstaltungen als `dozent`
+course-memberships | die Teilnahmen an Veranstaltungen
+events | der Terminkalender
+institute-memberships | die Institute
+schedule | der Stundenplan
+
+
+## Alle `users`
+
+```shell
+curl --request GET \
+ --url https://example.com/jsonapi.php/v1/users \
+ --header "Authorization: Basic `echo -ne "root@studip:testing" | base64`" \
+```
+
+ > Der Request liefert JSON ähnlich wie dieses:
+
+```json
+{
+ "meta": {
+ "page": {
+ "offset": 0,
+ "limit": 30,
+ "total": 5
+ }
+ },
+ "links": {
+ "first": "/?page[offset]=0&page[limit]=30",
+ "last": "/?page[offset]=0&page[limit]=30"
+ },
+ "data": [
+ {
+ "type": "users",
+ "id": "76ed43ef286fb55cf9e41beadb484a9f",
+ "attributes": {
+ "username": "root@studip",
+ "formatted-name": "Root Studip",
+ "family-name": "Studip",
+ "given-name": "Root",
+ "name-prefix": "",
+ "name-suffix": "",
+ "permission": "root",
+ "email": "root@localhost",
+ "phone": null,
+ "homepage": null,
+ "address": null
+ },
+ "relationships": {
+ "activitystream": {
+ "links": {
+ "related": "jsonapi.php/v1/users/76ed43ef286fb55cf9e41beadb484a9f/activitystream"
+ }
+ },
+ "blubber-postings": {
+ "links": {
+ "related": "jsonapi.php/v1/blubber-postings?filter[user]=76ed43ef286fb55cf9e41beadb484a9f"
+ }
+ },
+ "contacts": {
+ "links": {
+ "related": "jsonapi.php/v1/users/76ed43ef286fb55cf9e41beadb484a9f/contacts"
+ }
+ },
+ "courses": {
+ "links": {
+ "related": "jsonapi.php/v1/users/76ed43ef286fb55cf9e41beadb484a9f/courses"
+ }
+ },
+ "course-memberships": {
+ "links": {
+ "related": "jsonapi.php/v1/users/76ed43ef286fb55cf9e41beadb484a9f/course-memberships"
+ }
+ },
+ "events": {
+ "links": {
+ "related": "jsonapi.php/v1/users/76ed43ef286fb55cf9e41beadb484a9f/events"
+ }
+ },
+ "institute-memberships": {
+ "links": {
+ "related": "jsonapi.php/v1/users/76ed43ef286fb55cf9e41beadb484a9f/institute-memberships"
+ }
+ },
+ "schedule": {
+ "links": {
+ "related": "jsonapi.php/v1/users/76ed43ef286fb55cf9e41beadb484a9f/schedule"
+ }
+ }
+ },
+ "links": {
+ "self": "jsonapi.php/v1/users/76ed43ef286fb55cf9e41beadb484a9f"
+ },
+ "meta": [
+
+ ]
+ },
+ "[...]"
+ ]
+}
+```
+
+Dieser Endpoint liefert alle Nutzer*innen im Stud.IP, die mit den
+`credentials` des JSON:API-Nutzenden auch in Stud.IP selbst gesehen
+werden dürfen. Die Ausgabe erfolgt paginiert und kann durch Angabe von
+Offset und Limit weitergeblättert werden.
+
+### HTTP Request
+
+`GET /users`
+
+### Query-Parameter
+
+```shell
+curl --request GET \
+ --url 'https://example.com/jsonapi.php/v1/users?filter[search]=test_autor'\
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`"
+```
+
+Parameter | Default | Beschreibung
+--------- | ------- | ------------
+page[offset] | 0 | der Offset
+page[limit] | 30 | das Limit
+filter[search] | %%% | der Suchbegriff, um Nutzer zu finden; mind. 3 Zeichen
+
+### Autorisierung
+
+Diese Route kann nur von Nutzern der Rechtestufe "root" verwendet werden.
+
+
+
+## Sich selbst auslesen
+
+```shell
+curl --request GET \
+ --url https://example.com/jsonapi.php/v1/users/me \
+ --header "Authorization: Basic `echo -ne "test_dozent:testing" | base64`"
+```
+
+> Der Request liefert JSON ähnlich wie dieses:
+
+```json
+{
+ "data": {
+ "type": "users",
+ "id": "205f3efb7997a0fc9755da2b535038da",
+ "attributes": {
+ "username": "test_dozent",
+ "formatted-name": "Testaccount Dozent",
+ "family-name": "Dozent",
+ "given-name": "Testaccount",
+ "name-prefix": "",
+ "name-suffix": "",
+ "permission": "dozent",
+ "email": "dozent@studip.de",
+ "phone": null,
+ "homepage": null,
+ "address": null
+ },
+ "relationships": {
+ "activitystream": {
+ "links": {
+ "related": "jsonapi.php/v1/users/205f3efb7997a0fc9755da2b535038da/activitystream"
+ }
+ },
+ "blubber-postings": {
+ "links": {
+ "related": "jsonapi.php/v1/blubber-postings?filter[user]=205f3efb7997a0fc9755da2b535038da"
+ }
+ },
+ "contacts": {
+ "links": {
+ "related": "jsonapi.php/v1/users/205f3efb7997a0fc9755da2b535038da/contacts"
+ }
+ },
+ "courses": {
+ "links": {
+ "related": "jsonapi.php/v1/users/205f3efb7997a0fc9755da2b535038da/courses"
+ }
+ },
+ "course-memberships": {
+ "links": {
+ "related": "jsonapi.php/v1/users/205f3efb7997a0fc9755da2b535038da/course-memberships"
+ }
+ },
+ "events": {
+ "links": {
+ "related": "jsonapi.php/v1/users/205f3efb7997a0fc9755da2b535038da/events"
+ }
+ },
+ "institute-memberships": {
+ "links": {
+ "related": "jsonapi.php/v1/users/205f3efb7997a0fc9755da2b535038da/institute-memberships"
+ }
+ },
+ "schedule": {
+ "links": {
+ "related": "jsonapi.php/v1/users/205f3efb7997a0fc9755da2b535038da/schedule"
+ }
+ }
+ },
+ "links": {
+ "self": "jsonapi.php/v1/users/205f3efb7997a0fc9755da2b535038da"
+ },
+ "meta": [
+
+ ]
+ }
+}
+```
+
+Mit diesem Endpoint bekommt man denjenigen Stud.IP Nutzer, der
+autorisiert auf diesen Endpoint zugreift – also sich selbst.
+
+### HTTP Request
+
+`GET /users/me`
+
+### Query-Parameter
+
+Es werden keine Query-Parameter unterstützt.
+
+### Autorisierung
+
+Diese Route kann von jedem autorisierten Nutzer verwendet werden.
+
+
+## Einzelne Nutzende auslesen
+
+```shell
+curl --request GET \
+ --url https://example.com/jsonapi.php/v1/users/<ID> \
+ --header "Authorization: Basic `echo -ne "test_dozent:testing" | base64`"
+```
+
+Diese Route liefert einzelne, beliebige Nutzende zurück. Unsichtbare
+Nutzende können sich allerdings nur selbst sehen.
+
+### HTTP Request
+
+`GET /users/{id}`
+
+### Query-Parameter
+
+Es werden keine Query-Parameter unterstützt.
+
+### Autorisierung
+
+Man kann sich selbst sehen. `root` darf alle Nutzenden sehen. Gesperrte
+und unsichtbare Nutzende sind ansonsten nicht sichtbar.
+
+
+## Nutzende löschen
+
+```shell
+curl --request DELETE \
+ --url https://example.com/jsonapi.php/v1/users/<ID> \
+ --header "Authorization: Basic `echo -ne "test_dozent:testing" | base64`"
+```
+
+Diese Route löscht einen beliebigen Nutzenden.
+
+### HTTP Request
+
+`DELETE /users/{id}`
+
+### Query-Parameter
+
+Es werden keine Query-Parameter unterstützt.
+
+### Autorisierung
+
+Diese Route ist nur aktiviert, wenn die Stud.IP-Konfiguration
+"JSONAPI_DANGEROUS_ROUTES_ALLOWED" gesetzt ist.
+
+Ist das der Fall, dürfen Nutzende der Rechtestufe `root` andere
+Nutzende löschen. Man kann sich selbst **nicht** löschen.
+
+
+## Mitgliedschaften in Einrichtungen
+
+```shell
+curl --request GET \
+ --url https://example.com/jsonapi.php/v1/users/<ID>/institute-memberships \
+ --header "Authorization: Basic `echo -ne "test_dozent:testing" | base64`"
+```
+
+Mit dieser Route erhält man die Mitgliedschaften in Einrichtungen von Nutzenden.
+
+### HTTP Request
+
+`GET http://example.com/api/users/{id}/institute-memberships`
+
+### Query-Parameter
+
+Es werden keine Query-Parameter unterstützt.
+
+### Autorisierung
+
+Ein Nutzer kann nur die eigenen Mitgliedschaften in Einrichtungen einsehen.
diff --git a/docs/docs/jsonapi/wiki.md b/docs/docs/jsonapi/wiki.md
new file mode 100644
index 0000000..cf1dcdf
--- /dev/null
+++ b/docs/docs/jsonapi/wiki.md
@@ -0,0 +1,233 @@
+---
+title: Wiki
+---
+
+:::info
+Wikis bieten Dozenten und Studenten die Möglichkeit,
+gemeinsame Dokumente zu erstellen. Zu verschiedenen Themen in der
+Veranstaltungen können Wikis erstellt und gepflegt werden. Eine
+Versionskontrolle ermöglicht, sich Änderungen zwischen den
+Dokumentenversionen anzuschauen.
+:::
+
+## Schema "wiki-pages"
+Neben dem Inhalt und Namen enthält jede Wiki-Seite Metadaten zur aktuellen Version.
+### Attribute
+
+Attribut | Beschreibung
+-------- | ------------
+keyword | Der Name der Wiki-Seite
+content | Der Inhalt der Wiki-Seite
+chdate | Das Datum der letzten Änderung
+version | Die aktuelle Versionsnummer
+
+### Relationen
+
+ Relation | Beschreibung
+-------- | ------------
+author | Der Verfasser der Wiki-Seite
+range | Der Range einer Wiki-Seite ist der entsprechende Kurs
+
+## Wiki-Seiten eines Kurses
+ `GET /courses/{id}/wiki-pages`
+
+### Parameter
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID des Kurses
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/courses/<COURSE-ID>/wiki-pages \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ ```
+### Autorisierung
+ Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
+
+> Der Request liefert JSON ähnlich wie dieses:
+
+```json
+{
+ "meta": {
+ "page": {
+ "offset": 0,
+ "limit": 30,
+ "total": 2
+ }
+ },
+ "links": {
+ "first": "/?page[offset]=0&page[limit]=30",
+ "last": "/?page[offset]=0&page[limit]=30"
+ },
+ "data": [
+ {
+ "type": "wiki-pages",
+ "id": "a07535cf2f8a72df33c12ddfa4b53dde_ulyq",
+ "attributes": {
+ "keyword": "ulyq",
+ "content": "Es gibt im Moment in diese Mannschaft, oh, einige Spieler vergessen ihren Profi was sie sind. Ich lese nicht sehr viele Zeitungen, aberich habe geh\u00f6rt viele Situationen. Erstens: Wir haben nicht offensivgespielt.",
+ "chdate": "2019-04-23T12:10:26+02:00",
+ "version": 1
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "users",
+ "id": "e7a0a84b161f3e8c09b4a0a2e8a58147"
+ },
+ "links": {
+ "related": "jsonapi.php/v1/users/e7a0a84b161f3e8c09b4a0a2e8a58147"
+ }
+ },
+ "range": {
+ "data": {
+ "type": "courses",
+ "id": "a07535cf2f8a72df33c12ddfa4b53dde"
+ },
+ "links": {
+ "related": "jsonapi.php/v1/courses/a07535cf2f8a72df33c12ddfa4b53dde"
+ }
+ }
+ }
+ },
+ {
+ "type": "wiki-pages",
+ "id": "a07535cf2f8a72df33c12ddfa4b53dde_yxilo",
+ "attributes": {
+ "keyword": "yxilo",
+ "content": "Es gibt im Moment in diese Mannschaft, oh, einige Spieler vergessen ihren Profi was sie sind. Ich lese nicht sehr viele Zeitungen, aberich habe geh\u00f6rt viele Situationen. Erstens: Wir haben nicht offensivgespielt.",
+ "chdate": "2019-04-23T12:10:26+02:00",
+ "version": 1
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "users",
+ "id": "e7a0a84b161f3e8c09b4a0a2e8a58147"
+ },
+ "links": {
+ "related": "jsonapi.php/v1/users/e7a0a84b161f3e8c09b4a0a2e8a58147"
+ }
+ },
+ "range": {
+ "data": {
+ "type": "courses",
+ "id": "a07535cf2f8a72df33c12ddfa4b53dde"
+ },
+ "links": {
+ "related": "jsonapi.php/v1/courses/a07535cf2f8a72df33c12ddfa4b53dde"
+ }
+ }
+ }
+ },
+ "[...]"
+ ]
+}
+```
+
+## Wiki-Seite
+
+Gibt eine Wiki-Seite zurück.
+
+ `GET /wiki-pages/{id}`
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID der Wiki-Seite
+
+
+ ```shell
+ curl --request GET \
+ --url https://example.com/wiki-pages/<ID> \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ ```
+### Autorisierung
+ Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
+ > Der Request liefert JSON ähnlich wie dieses:
+
+```json
+{
+ "data": {
+ "type": "wiki",
+ "id": "48101a5a47c34f80999cc01266b32536_tastyTest",
+ "attributes": {
+ "keyword": "tastyTest",
+ "content": "This is dsdsadsad",
+ "chdate": "2018-06-05T14:12:29+02:00",
+ "version": 1
+ },
+ "relationships": {
+ "author": {
+ "data": {
+ "type": "users",
+ "id": "76ed43ef286fb55cf9e41beadb484a9f"
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/users/76ed43ef286fb55cf9e41beadb484a9f"
+ }
+ },
+ "range": {
+ "data": {
+ "type": "courses",
+ "id": "48101a5a47c34f80999cc01266b32536"
+ },
+ "links": {
+ "related": "/stud35/plugins.php/argonautsplugin/courses/48101a5a47c34f80999cc01266b32536"
+ }
+ }
+ },
+ "links": {
+ "self": "/stud35/plugins.php/argonautsplugin/courses/48101a5a47c34f80999cc01266b32536/wiki/tastyTest"
+ }
+ }
+}
+```
+
+
+## Wiki-Seite anlegen
+
+Legt eine Wiki-Seite an.
+
+ `POST /courses/{id}/wiki-pages`
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID der Veranstaltung
+
+ ```shell
+ curl --request POST \
+ --url https://example.com/courses/<COURSE-ID>/wiki \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ '{"data": {"type": "wiki-pages","attributes": {"keyword": "testing","content": "wiki created"}}}'
+ ```
+### Autorisierung
+ Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
+
+
+## Wiki-Seite ändern
+
+Aktualisiert eine Wiki-Seite.
+
+ `PATCH /wiki-pages/{id}`
+
+ Parameter | Beschreibung
+ ---------- | ------------
+ id | Die ID der Wiki-Seite
+
+```
+curl --request PATCH \
+ --url https://example.com/wiki-pages/<ID> \
+ --header "Content-Type: application/vnd.api+json" \
+ --header "Authorization: Basic `echo -ne "test_autor:testing" | base64`" \
+ --data
+ '{"data": {"type": "wiki-pages","attributes": {"content": "wiki changed"}}}'
+```
+
+### Autorisierung
+Der Nutzer sollte Mitglied des entsprechenden Kurses sein.
diff --git a/docs/docs/landing-page.md b/docs/docs/landing-page.md
new file mode 100644
index 0000000..934bfac
--- /dev/null
+++ b/docs/docs/landing-page.md
@@ -0,0 +1,52 @@
+# Stud.IP Entwicklungs-Dokumentation
+
+<div class="landing-page-box">
+ <div>
+ <a href="./quickstart/">
+ <img src="https://develop.studip.de/studip/assets/images/icons/blue/play.svg" />
+ <div><strong>Quickstart</strong></div>
+ </a>
+ </div>
+ <div>
+ <a href="./start">
+ <img src="https://develop.studip.de/studip/assets/images/icons/blue/infopage.svg" />
+ <div><strong>Dokumentation</strong></div>
+ </a>
+ </div>
+ <div>
+ <a href="./rules/introduction">
+ <img src="https://develop.studip.de/studip/assets/images/icons/blue/source.svg" />
+ <div><strong>Organisation</strong></div>
+ </a>
+ </div>
+</div>
+
+**Weitere Anlaufpunkte**
+
+<div class="landing-page-box">
+ <div>
+ <a href="https://gitlab.studip.de/studip/studip/-/issues/new">
+ <img src="https://develop.studip.de/studip/assets/images/icons/blue/exclaim-circle-full.svg" />
+ <div><strong>Fehler / BIEST berichten</strong></div>
+ </a>
+ </div>
+ <div>
+ <a href="https://develop.studip.de/">
+ <img src="https://develop.studip.de/studip/assets/images/icons/blue/community.svg" />
+ <div><strong>Kontakt zur Community</strong></div>
+ </a>
+ </div>
+ <div>
+ <a href="https://matrix.to/#/%23Stud.IP:matrix.org">
+ <img src="https://develop.studip.de/studip/assets/images/icons/blue/chat.svg" />
+ <div><strong>Entwicklungs-Chat</strong></div>
+ </a>
+ </div>
+ <div>
+ <a href="https://hilfe.studip.de/help/5.0/de/Basis.Allgemeines">
+ <img src="https://develop.studip.de/studip/assets/images/icons/blue/question-circle.svg" />
+ <div><strong>Anwendungs-Dokumentation</strong></div>
+ </a>
+ </div>
+</div>
+
diff --git a/docs/docs/plugins/allgemeines.md b/docs/docs/plugins/allgemeines.md
new file mode 100644
index 0000000..b8a6ed8
--- /dev/null
+++ b/docs/docs/plugins/allgemeines.md
@@ -0,0 +1,454 @@
+---
+id: plugins
+title: Plugins
+sidebar_label: Allgemeines
+---
+
+### Einleitung
+
+Da jeder Standort, an dem Stud.IP eingesetzt wird, ganz eigene Anforderungen oder Einschränkungen hat, wird mit der Plugin-Schnittstelle ein Mechanismus angeboten, über den man eigene Funktionen zu Stud.IP hinzufügen kann, ohne dabei das Kernsystem anfassen zu müssen. Das Aktualisieren, Entfernen oder Hinzufügen von Komponenten ist dabei im laufenden Betrieb möglich.
+
+Plugins können eigene Seiten im Stud.IP-System anbieten, die an bestimmten Stellen in die Navigationsstruktur eingebunden werden können, z.B. als neuer Reiter in einer Veranstaltung. Darüber hinaus verfügen bestimmte Plugin-Typen auch über die Möglichkeit, auf bestehende Seiten Einfluß zu nehmen und damit z.B. einen eigenen Block auf der Stud.IP-Startseite anzuzeigen.
+
+### Was ist ein Stud.IP-Plugin?
+
+Ein Plugin ist eine ZIP-Datei, die eine Beschreibung des Pugins (Name, Version, usw.), den Programmcode und ggf. von Plugin mitgebrachte Ressourcen (Bilder, Stylesheets usw.) enthält. Im einzelnen kann ein Plugin-Paket die folgenden Komponenten enthalten:
+
+* eine Manifest-Datei mit dem Namen `plugin.manifest`
+* mindestens eine PHP-Klasse mit dem Programmcode des Plugins
+* optional weitere Dateien mit statischen Inhalten (Bilder, CSS-Stylesheets, JavaScript-Dateien) oder PHP-Bibliotheken
+* optional ein SQL-Skript zur Erzeugung des Datenbankschemas für das Plugin
+* optional ein SQL-Skript zum Löschen des Datenbankschemas für das Plugin
+* optional ein Unterverzeichnis mit dem Namen `migrations`, welches [Migrations-Dateien](../functions/migrations.md) enthält
+* optional ein Unterverzeichnis mit dem Namen `locale`, das [Übersetzungsdateien](../quickstart/internationalisierung.md) enthält
+* optional ein Unterverzeichnis mit dem Namen `templates` mit Templates zur Darstellung
+
+Ein Beispiel für die Verzeichnisstruktur in einem Plugin-Paket:
+
+```ini
+MyPlugin.class.php
+ plugin.manifest
+ images/
+ icon.png
+ migrations/
+ 1_test.php
+ 2_foobar.php
+ sql/
+ install.sql
+ uninstall.sql
+ stylesheets/
+ grid.css
+ templates/
+ page.php
+```
+
+
+### Bestandteile eines Plugins
+
+In der folgenden Abschnitten werden die verschiedenen Bestandteile eines Plugin-Pakets der Reihe nach erklärt:
+
+#### Plugin-Manifest
+
+Jedes Plugin-Paket muß ein sogenanntes "Plugin-Manifest" enthalten, in dem wichtige Informationen über das Plugin für die Installation und Verwaltung des Plugins enthalten sind. Das Plugin-Manifest liegt immer im Wurzelverzeichnis des Plugins und hat den Namen `plugin.manifest`. Es ist eine Textdatei und kann bzw. muß folgende Einträge enthalten:
+
+| Wert | Beschreibung |
+| ---- | ---- |
+|**pluginname** | Der Name des Plugins. Dieser dient zur eindeutigen Identifizierung im System (d.h. es können nicht zwei Plugins gleichen Namens installiert werden). Für die Anzeige innerhalb des Systems kann das Plugin selbst auch noch einen anderen Namen liefern. |
+|**pluginclassname** | Der Name der Plugin-Klasse, also der PHP-Klasse, die von einer der Basisklassen der Pluginschnittstelle abgeleitet wurde. Im Manifest dürfen mehrere solcher Plugin-Klassen angegeben werden, wobei die "Hauptklasse" als erste aufgeführt werden muß. Dies dient dazu, in einem Plugin-Paket mehrere Plugin-Einstiegspunkte zu definieren, z.B. könnte das Plugin einen Einstiegspunkt über die Startseite und auch über die Veranstaltungen definieren. |
+|**origin** | Der Ursprung des Plugins, üblicherweise der Name des Programmierers oder der Institution oder Gruppe, zu der dieser gehört. |
+|**version** | Die Version des Plugins. Die Version sollte so gewählt werden, daß ein Vergleich mit der PHP-Funktion `version_compare()` sinnvoll möglich ist. |
+|**description (optional)** | Eine Kurzbeschreibung des Plugins. Diese wird im System den Nutzern angezeigt, wenn das Plugin zur Aktivierung angeboten wird. |
+|**homepage (optional)** | Eine URL zur Homepage des Plugins, die weitere Informationen über dieses Plugin enthält. Das kann z.B. die entsprechende Seite über das Plugin im offiziellen Plugin-Repository von Stud.IP sein. |
+|**dbscheme (optional)** | Verweis auf eine Datei mit einem SQL-Skript, die sich innerhalb des Plugin-Pakets befindet. Dieses wird bei der Installation des Plugins ausgeführt (nicht aber bei Updates). Hier können Tabellen und Tabelleninhalte angelegt werden, die das Plugin benötigt. Der Dateiname ist dabei relativ anzugeben. |
+|**uninstalldbscheme (optional)** | Verweis auf eine Datei mit einem SQL-Skript, die sich innerhalb des Plugin-Pakets befindet. Dieses wird unmittelbar vor dem Entfernen des Plugins ausgeführt. Hier können Tabellen und Tabelleninhalte wieder gelöscht werden, die das Plugin angelegt hat. Der Dateiname ist dabei relativ anzugeben. |
+|**updateURL (optional)** | Verweis auf eine URL mit Update-Informationen zu diesem Plugin. Wenn dieser Eintrag vorhanden ist, kann das Plugin über die Update-Funktion der Plugin-Verwaltung aktualisiert werden. Fehlt der Eintrag, ist ein automatisches Update nur dann möglich, wenn die zentrale Meta-Datei des Plugin-Repositories Update-Informationen zu diesem Plugin enthält. Wenn der Eintrag `updateURL` gesetzt ist, muß er auf eine XML-Datei von der folgenden Struktur verweisen: |
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<plugins>
+ <plugin name="Demo">
+ <release
+ version="1.2"
+ url="http://plugins.studip.de/uploads/Plugins/demo-1.2.zip"
+ studipMinVersion="1.6.0"
+ studipMaxVersion="1.8.5" />
+ <release
+ version="2.0"
+ url="http://plugins.studip.de/uploads/Plugins/demo-2.0.zip"
+ studipMinVersion="1.8.0" />
+ </plugin>
+</plugins>
+```
+
+-Eine solche Datei mit Meta-Daten kann durchaus auch Informationen über viele verschiedene Plugins (mehrere `plugin`-Elemente) und verschiedene Versionen eines Plugins (d.h. mehrere `release`-Elemente) enthalten.
+
+| Wert | Beschreibung |
+| ---- | ---- |
+|**studipMinVersion (optional)** | Angabe der minimalen Stud.IP-Version, mit der dieses Plugin kompatibel ist. Versucht man, es in einer älteren Version zu installieren, erhält man eine entsprechende Fehlermeldung und die Installation schlägt fehlt. |
+|**studipMaxVersion (optional)** | Angabe der maximalen Stud.IP-Version, mit der dieses Plugin noch lauffähig ist. Versucht man, es in einer neueren Version zu installieren, erhält man eine entsprechende Fehlermeldung und die Installation schlägt fehlt. |
+
+
+Mit der Neugestaltung der "+"-Seite in der Version 3.2 sind neue optionale Informationen für das Plugin-Manifest, welche der Visualisierung auf der "+"-Seite dienen, hinzu gekommen.
+
+| Wert | Beschreibung |
+| ---- | ---- |
+|**category (optional)** | Die Kategorie, unter der das Plugin auf der "+"-Seite angezeigt werden soll. Die vorhandenen Kategorien sind: |
+
+* *Lehrorganisation*
+* *Kommunikation und Zusammenarbeit*
+* *Aufgaben*
+* *Sonstiges*
+
+Wenn keine Kategorie angegeben ist, wird das Plugin automatisch unter Sonstiges gelistet. Das Angeben einer nicht vorhandenen Kategorie fügt automatisch diese Kategorie neu hinzu.
+
+| Wert | Beschreibung |
+| ---- | ---- |
+|**displayname (optional)** | Der Name mit dem das Plugin auf der "+"-Seite angezeigt werden soll. Wenn kein displayname angegeben ist wird an entsprechender Stelle der Pluginname verwendet. |
+|**complexity (optional)** | Komplexitätsgrad des Plugins, unterteilt in 1 für Standard, 2 für Erweitert oder 3 für Intensiv. |
+|**icon (optional)** | Pfad zum Icon des Plugins zur Darstellung auf der "+"-Seite. Dieser Pfad ist relativ zum Wurzelverzeichnis des Plugins anzugeben. |
+|**descriptionshort (optional)** | Eine kurze Beschreibung des Plugins, welche auch im zugeklappten Zustand der "+"-Seite zu sehen ist. |
+|**descriptionlong (optional)** | Eine ausführliche Beschreibung des Plugins, welche im aufgeklappten Zustand der "+"-Seite zu sehen ist. |
+|**screenshot (optional)** | Pfad zu einem Screenshot des Plugins, welcher zur Darstellung auf der "+"-Seite verwendet wird. Im Manifest dürfen mehrere solcher Screenshots angegeben werden, wobei der Screenshot, welcher groß dargestellt wird, als erste aufgeführt werden muss. Dies dient dazu, auf der "+"-Seite mehrere Screenshots darzustellen. Die Reihenfolge der screenshot Einträge im Manifest ist auch die Reihenfolge der Darstellung. Als Beschreibungstext des Bildes wird der Dateiname ohne die Dateiendung angezeigt. Dieser Pfad ist relativ zum Wurzelverzeichnis des Plugins anzugeben. |
+|**keywords (optional)** | Stichworte die das Plugin kurz und knapp beschreiben. Diese Stichwörter werden auf der "+"-Seite als Liste angezeigt. Mehrere Einträge sind durch ein Semikolon zu trennen. |
+|**helplink (optional)** | Verweis auf eine URL mit Hilfe-Informationen zum Plugin. Bei Angabe der URL wird auf der "+"-Seite ein entsprechender Verweis angezeigt. |
+
+Ein Beispiel für ein vollständiges Manifest:
+
+```ini
+pluginname=Demo
+pluginclassname=DemoPlugin
+origin=virtUOS
+version=1.2
+dbscheme=sql/install.sql
+uninstalldbscheme=sql/uninstall.sql
+updateURL=http://plugins.studip.de/svn/plugins/plugins.xml
+studipMinVersion=1.6.0
+studipMaxVersion=1.8.5
+
+category=Sonstiges
+displayname=Demo
+complexity=1
+icon=images/icons/demoicon.jpg
+descriptionshort=Demonstration eines Plugins
+descriptionlong=Dieser Text kann ruhig etwas länger sein
+screenshot=images/screenshots/demo.jpg
+screenshot=images/screenshots/noch_eine_demo.png
+keywords=demo;manifest;plugin
+helplink=http://hilfe.studip.de/develop/Entwickler/PluginSchnittstelle
+```
+
+#### SQL-Skript zur Erzeugung des Datenbankschemas
+
+Innerhalb eines Plugin-Pakets kann sich ein SQL-Skript befinden, welches mit Semikolon abgeschlossene SQL-Befehle enthält. Dieses SQL-Skript dient dem initialen Anlegen einer oder mehrerer Datenbank-Tabellen für das Plugin. Es wird während der Installation des Plugins von Stud.IP ausgeführt.
+
+#### SQL-Skript zum Löschen des Datenbankschemas
+
+Analog zu den Regeln für ein SQL-Skript zur Erzeugung des Datenschemas für das Plugin läßt sich auch ein Skript definieren, welches unmittelbar vor dem Entfernen des Plugins aus Stud.IP ausgeführt wird.
+
+### Plugin-Klasse
+
+Jedes Plugin muß mindestens eine Klasse enthalten, die die für die Einbettung in die Stud.IP-Umgebung erforderlichen Funktionen des Plugins implementiert. Natürlich können daneben beliebig viele weitere Klassen im Plugin-Paket enthalten sein.
+
+Die Plugin-Klasse muß den im Manifest unter **pluginclassname** angegebenen Namen haben und von der Klasse `StudIPPlugin` abgeleitet sein. Außerdem sollte die Klasse mindestens ein Interface implementieren, um sich an bestimmten Stellen in ein bestehendes Stud.IP-System einklinken zu können.
+
+#### Standard-Methoden eines Plugins
+
+Durch das Ableiten von der Klasse `StudIPPlugin` besitzt jedes Plugin automatisch eine Reihe von Methoden:
+
+| Wert | Beschreibung |
+| ---- | ---- |
+|**getPluginId()** | Liefert die ID des Plugins. Die ID wird intern zur Verwaltung des Plugins verwendet. |
+|**getPluginName()** | Liefert den im Manifest definierten Namen des Plugins. |
+|**getPluginPath()** | Liefert einen Dateisystempfad zum Verzeichnis des Plugins. Dies kann z.B. verwendet werden, um Ausgabe-Templates zu laden. |
+|**getPluginURL()** | Liefert eine (absolute) URL zum Installationsort des Plugins. Wenn man im Plugin auf Style-Sheets oder Bilder verweisen möchte, sollte man diese URL verwenden. |
+|**isActivated($context = NULL)** | Prüft, ob das Plugin im angegebenen Kontext (z.B. der aktuellen Veranstaltung) aktiviert ist oder nicht. Falls kein Kontext übergeben wird, ist die aktuell gewählte Veranstaltung gemeint. |
+|**isActivatableForContext(Range $context)** | Über diese Methode kann das Plugin selbst entscheiden, ob es in dem übergebenen Kontext aktiviert werden kann, sprich auf der "Mehr"-Seite auftaucht. |
+|**deactivationWarning($context)** | Liefert einen Warntext, der ausgegeben wird, bevor das Plugin im angegebenen Kontext deaktiviert wird. Hier kann man z.B. auf eventuellen Datenverlust hinweisen. Die Implementierung der Basisklasse muß dafür überschrieben werden. |
+|**perform($unconsumed_path)** | Zeigt eine Seite des Plugins an. TODO: Das muß noch genauer beschrieben werden. |
+
+#### Plugin-Interfaces
+
+Um an bestimmten Stellen in Stud.IP aktiv werden zu können, muss ein Plugin noch eines oder mehrere der Plugin-Interfaces implementieren. In der Version 1.11 stehen dafür die folgenden Schnittstellen bereit:
+
+##### HomepagePlugin: Homepage eines Nutzers
+
+Homepage-Plugins werden nur im Homepage-Kontext geladen. Sie können auf der Homepage eigene Navigationspunkte einblenden und einen Informationsblock auf der Übersichtsseite der Homepage anzeigen.
+
+Dieses Interface enthält die folgende Methode:
+
+| Wert | Beschreibung |
+| ---- | ---- |
+|**getHomepageTemplate($user_id)** | Liefert ein Template, das auf der Übersichtsseite des Benutzers angezeigt wird. Wenn das Plugin dort nicht angezeigt werden soll, sollte die Methode `NULL` liefern. Zur Konfiguration des Anzeigebereichs kann das Plugin im Template neben den eigenen Platzhaltern noch einige spezielle Werte setzen (Voreinstellungen in eckigen Klammern): |
+
+| Wert | Beschreibung |
+| ---- | ---- |
+| *title* | Anzeigetitel [Name des Plugins] |
+| *icon_url* | Plugin-Icon [kein Icon] |
+| *admin_url* | Administrations-Link [kein Link] |
+| *admin_title* | Beschriftung für den Administrations-Link [Administration] |
+
+##### PortalPlugin: Startseite (Portalseite)
+
+Portal-Plugins werden auf der Startseite geladen, auch wenn der Benutzer (noch) nicht angemeldet ist. Sie können eigene Navigationspunkte auf der Login- und Startseite einblenden und einen Informationsblock auf der Startseite anzeigen.
+
+Dieses Interface enthält die folgende Methode:
+
+| Wert | Beschreibung |
+| ---- | ---- |
+|**getPortalTemplate()** | Liefert ein Template, das auf der Startseite des Systems angezeigt wird. Wenn das Plugin dort nicht angezeigt werden soll, sollte die Methode `NULL` liefern. Zur Konfiguration des Anzeigebereichs kann das Plugin im Template neben den eigenen Platzhaltern noch einige spezielle Werte setzen (Voreinstellungen in eckigen Klammern): |
+
+| Wert | Beschreibung |
+| ---- | ---- |
+| *title* | Anzeigetitel [Name des Plugins] |
+| *icon_url* | Plugin-Icon [kein Icon] |
+| *admin_url* | Administrations-Link [kein Link] |
+| *admin_title* | Beschriftung für den Administrations-Link [Administration] |
+
+##### StandardPlugin: Veranstaltungen und Einrichtungen
+
+Standard-Plugins werden nur im Veranstaltungs- und Einrichtungs-Kontext geladen (allerdings zur Zeit nicht im Admin-Bereich). Sie können in der Veranstaltung bzw. Einrichtung eigene Navigationspunkte einblenden und ein Icon mit einem Link zum Plugin auf der Seite "Meine Veranstaltungen" anzeigen.
+
+Dieses Interface enthält die folgenden Methoden:
+
+| Wert | Beschreibung |
+| ---- | ---- |
+|**getIconNavigation($course_id, $last_visit)** | Liefert ein Navigationsobjekt für das Icon des Plugins auf der Seite "Meine Veranstaltungen". Wenn das Plugin dort nicht angezeigt werden soll, sollte die Methode `NULL` liefern. *$last_visit* ist der Zeitpunkt des letzten Besuchs des Nutzers in der Veranstaltung (bzw. des Plugins). Wenn es seit diesem Zeitpunkt neue oder geänderte Inhalte gibt, sollte dies über ein spezielles Icon dem Nutzer kenntlich gemacht werden. |
+|**getInfoTemplate($course_id)** | Liefert ein Template, das auf der Kurzinfoseite der Veranstaltung bzw. Einrichtung angezeigt wird. Wenn das Plugin dort nicht angezeigt werden soll, sollte die Methode `NULL` liefern. Zur Konfiguration des Anzeigebereichs kann das Plugin im Template neben den eigenen Platzhaltern noch einige spezielle Werte setzen (Voreinstellungen in eckigen Klammern): |
+
+| Wert | Beschreibung |
+| ---- | ---- |
+| *title* | Anzeigetitel [Name des Plugins] |
+| *icon_url* | Plugin-Icon [kein Icon]|
+| *admin_url* | Administrations-Link [kein Link]|
+| *admin_title* | Beschriftung für den Administrations-Link [Administration]|
+
+
+##### StudienmodulManagementPlugin: Studienmodulsuche
+
+Das StudienmodulManagementPlugin wird in der Anzeige von Studienbereichen verwendet, um weitere modulespezifische Informationen anzeigen zu können.
+
+Dieses Interface enthält die folgenden Methoden:
+
+| Wert | Beschreibung |
+| ---- | ---- |
+|**getModuleTitle($module_id, $semester_id = null)** | Gibt die Bezeichnung für ein Modul zurück. |
+|**getModuleDescription($module_id, $semester_id = null)** | Gibt die Kurzbeschreibung für ein Modul zurück. |
+|**getModuleInfoNavigation($module_id, $semester_id = null)** | Gibt ein Objekt vom Typ Navigation zurück, das Titel, Link und Icon für ein Modul enthalten kann, z.B. zur Darstellung eines Info-Icons. |
+
+##### SystemPlugin: systemweite Erweiterungen
+
+System-Plugins werden auf jeder Seite in Stud.IP geladen (mit Ausnahme von Seiten, die eine komplett andere Darstellung verwenden wie z.B. Druckansichten). Sie können überall im System eigene Navigationspunkte einblenden.
+
+Dieses Interface enthält keine Methoden.
+
+##### WebServicePlugin: Web-Services aus Plugins
+
+Plugins haben die Möglichkeit, die SOAP/XMLRPC-Webservices, die Stud.IP zur Verfügung stellen kann, um eigene Services zu erweitern. Dazu muss ein Plugin lediglich das WebServicePlugin-Interface implementieren:
+
+| Wert | Beschreibung |
+| ---- | ---- |
+|**getWebServices()** | Sollte Plugin-Services laden und liefert dann eine Liste von Service-Klassennamen zurück, die die systemeigenen ergänzen. |
+
+Beispiel:
+
+```php
+[...]
+function getWebServices()
+{
+ require 'MyService1.php';
+ require 'MyService2.php';
+ return array('MyService1', 'MyService2');
+}
+[...]
+```
+
+### Plugin-Aktionen
+
+Über die oben beschriebenen typspezifischen Fähigkeiten hinaus, d.h. insbesondere der Einbettung in vorhandene Seiten in Stud.IP, hat jedes Plugin die Möglichkeit, komplett eigene Seiten - inklusive einer eigenen Navigation - anzubieten. Dazu gibt es in der Plugin-Schnittstelle einen Mechanismus, der vom Nutzer aufgerufene URLs in Methodenaufrufe im Plugin übersetzt, in dieser Beschreibung als "Plugin-Aktionen" bezeichnet. Eine solche Plugin-Aktion ist eine normale (öffentliche) Methode in der Plugin-Klasse, deren Name auf "`_action`" endet:
+
+```php
+class TestPlugin extends StudipPlugin implements SystemPlugin
+{
+ [...]
+ public function delete_action($id)
+ {
+ [...]
+ }
+}
+```
+
+Eine Aktion kann Funktionsparameter haben (hier im Beispiel: `$id`), aber auch Request-Parameter verarbeiten (z.B. beim Absenden eines Formulars).
+
+#### Navigation im Plugin
+
+Das Erstellen von Navigationspunkten für Plugins ist [an anderer Stelle](Navigation) beschrieben und funktioniert genauso wie im Stud.IP-Kernsystem. Die zugehörigen URLs führen in der Regel zu bestimmten Aktionen im Plugin, deren Erstellung im folgenden Abschnitt beschrieben ist.
+
+#### Erstellen von URLs zu Plugin-Aktionen
+
+Damit der Nutzer eine bestimmte Aktion aufrufen kann, muß natürlich das Plugin auch die zugehörige URL kennen. Um URLs zu diesen Plugin-Aktionen erstellen zu können, gibt es zwei Hilfsfunktionen in der Klasse `PluginEngine`:
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**getLink($plugin_action, $params = array())** | Liefert die URL zu einer Aktion in einem Plugin. Die Aktion wird dabei durch den Klassennamen des Plugins, den Namen der Aktion sowie weitere Funktionsparameter spezifiziert, jeweils getrennt durch einen Schrägstrich ("`/`"). Der Name der Aktion darf fehlen, dann wird die Standardaktion mit dem Namen "`show`" in der angegebenen Plugin-Klasse verwendet. Als zweites Argument kann optional noch ein Array mit Request-Parametern (d.h. GET-Parametern) angegeben werden.|
+
+```php
+<a href="<?= PluginEngine::getLink("testplugin/delete/$id") ?>"> Eintrag löschen </a>
+```
+
+Das Resultat dieser Funktion ist eine *entity-kodierte URL*, d.h. es kann direkt in Attribute im HTML eingesetzt werden (*action* einer FORM, *href* eines A-Elements). Braucht man die unkodierte URL, sollte `getURL()` verwendet werden.
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**getURL($plugin_action, $params = array())** | Diese Funktion arbeitet genau wie `getLink()`, liefert aber keinen entity-kodierten Wert zurück, sondern die unkodierte URL. Diese kann dann z.B. für Aufrufe über JavaScript oder Redirects verwendet werden. Beispiel:[<<](<<) |
+
+```php
+header('Location: ' . $PluginEngine::getURL('testplugin/show'));
+```
+
+
+### Interaktion mit anderen Plugins
+
+Gelegentlich ist es auch wünschenswert, mit anderen Plugins interagieren zu können. Dazu bietet die Klasse `PluginEngine` ebenfalls eine Reihe von Hilfsfunktionen:
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**getPlugin($class)** | Liefert das Plugin mit dem angegebenen Klassennamen. Falls ein solches Plugin nicht installiert ist oder der Nutzer nicht die notwendigen Rechte besitzt, bekommt man statt der Plugin-Instanz nur einen `NULL`-Wert. |
+|**getPlugins($type, $context = NULL)** | Liefert alle Plugins des angegebenen Typs (Name eines Plugin-Interface) als Array. Auch hier werden natürlich nur solche Plugins gefunden, die der aktuelle Nutzer sehen darf. |
+|**sendMessage($type, $method, ...)** | Ruft die angegebene Methode bei allen Plugins eines bestimmten Typs (Name eines Plugin-Interface) auf. Auf den Namen folgende Argumente werden als Methodenparameter an jedes Plugin weitergereicht. Als Resultat bekommt man ein Array mit den Resultaten der einzelnen Methodenaufrufe. |
+|**sendMessageWithContext($type, $context, $method, ...)** | Ruft die angegebene Methode bei allen Plugins eines bestimmten Typs auf, die in einem bestimmten Kontext (z.B. die ID einer Veranstaltung oder Einrichtung) aktiviert sind. Auf den Namen folgende Argumente werden als Methodenparameter an jedes Plugin weitergereicht. Als Resultat bekommt man ein Array mit den Resultaten der einzelnen Methodenaufrufe. |
+
+### CSS und Javascript in Plugins
+
+Die Basisklasse `StudIPPlugin` stellt die Methode `addStylesheet()` und ab Version 4.4 analog die Methode `addScript()` bereit. Diesen Methoden kann ein zum Pluginpfad relativer Dateiname angegeben werden, um CSS/LESS-Stylesheets und Javascript-Dateien einzubinden. CSS und Javascript-Dateien werden ohne weitere Behandlung ausgegeben, während LESS-Dateien kompiliert werden. Ist das System im Entwicklungsmodus, so wird das LESS bei jeder Änderung der Datei neu kompiliert. Im Produktivmodus wird das LESS nur bei jeder Änderung der Pluginversion im Manifest neu kompiliert.
+
+Ab Version 4.4 bietet die Basisklasse auch die Methoden `addStylesheets()` und `addScripts()` an, um mehrere Dateien auf einmal einzubinden. Im Entwicklungsmodus werden diese so eingebunden als würden die Methoden für die einzelnen Methoden aufgerufen. Im Produktivmodus hingegen werden die eingebundenen Dateien aneinander gehängt und als eine einzige Datei ausgegeben.
+
+Alle hier erwähnten Methoden akzeptieren den Parameter `$link_attr` über welchen Attribute an das erzeugte HTML-Element gehängt werden können. Bei LESS-Stylesheets gibt es zusätzlich den Parameter `$variables`, über welchen Variablen reingereicht werden können, die dann im LESS zur Verfügung stehen.
+
+### Weitere Pluginmethoden
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**onEnable($plugin_id)** | Beim Aktivieren eines Plugins wird diese Methode der Klasse aufgerufen, damit das Plugin entsprechend reagieren oder Abhängigkeiten überprüfen kann. Für den Fall, dass das Plugin das Aktivieren verhindern möchte (bspw. weil benötigte Konfigurationen noch nicht vorgenommen worden), muß die Methode `onEnable` den Wert `false` zurückliefern. |
+|**onDisable($plugin_id)** | Beim Deaktivieren eines Plugins wird diese Methode der Klasse aufgerufen, damit das Plugin entsprechend reagieren oder Abhängigkeiten überprüfen kann. Für den Fall, dass das Plugin das Deaktivieren verhindern möchte, muß die Methode `onDisable` den Wert `false` zurückliefern. |
+
+### Temporäres Deaktivieren aller Plugins
+
+Über den URL-Parameter `disable_plugins=1` können Root-Administratoren alle Plugins temporär (d.h. für die laufende Session) deaktivieren, falls es zu Problemen nach dem Update eines Plugins kommen sollte und das System mit aktivierten Plugins nicht mehr nutzbar ist. Der Parameter kann auch vor dem Login gesetzt werden (falls schon die Login-Seite nicht mehr aufrufbar ist) und gilt dann für die daran anschließende Session bis zum Logout. Über `disable_plugins=0` kann es auch ohne Logout wieder zurückgesetzt werden.
+
+
+## Datenschutz in Plugins
+
+Damit Stud.IP auf Nutzeranfrage hin die im System gespeicherten nutzerbezogenen Daten aus Plugins mit ausliefern kann, ist es notwendig dass das Plugin das Interface `PrivacyPlugin` implementiert und die Funktion `exportUserData(StoredUserData $storage)` besitzt. Diese Funktion erhält eine Instanz der `StoredUserData` Klasse und kann darin die gespeicherten personenbezogenen Daten (sowohl tabellarische Daten als auch Dateien) ablegen.
+
+Beim Löschen von Personen wird das Event `UserDidDelete` gesendet, woraufhin ein Plugin auch seine nutzerbezogenen Daten zu dieser Person aus dem System löschen sollte.
+Werden einzelne Teile von Personendaten gelöscht, zum Beispiel zum Anonymisieren einer Person, wird das Event `UserDataDidRemove` gesendet. Dieses Event liefert als weiteren Parameter noch den Typ der gelöschten Personendaten. Die verfügbaren Typen können dem Beispiel unten entnommen werden. Welche dieser Typen für das Plugin relevant sind hängt von den durch das Plugin gespeicherten Daten ab.
+
+Ein Plugin könnte folgendermaßen aussehen:
+```php
+class MyPlugin extends StudIPPlugin implements StandardPlugin, PrivacyPlugin
+{
+
+ public function __construct()
+ {
+ parent::__construct();
+ NotificationCenter::addObserver($this, 'deleteUser', 'UserDidDelete');
+ NotificationCenter::addObserver($this, 'removeData', 'UserDataDidRemove');
+ }
+ ...
+
+ /**
+ * Export available data of a given user into a storage object
+ * (an instance of the StoredUserData class) for that user.
+ *
+ * @param StoredUserData $store object to store data into
+ */
+ public function exportUserData(StoredUserData $storage)
+ {
+ $db = DBManager::get();
+
+ $table_data = $db->fetchAll('SELECT * FROM my_table WHERE user_id = ?', [$storage->user_id]);
+ $storage->addTabularData('Anzeigetitel', 'my_table', $table_data);
+
+ $file_data = $db->fetchAll('SELECT * FROM my_files WHERE user_id = ?', [$storage->user_id]);
+ foreach ($file_data as $file) {
+ $storage->addFileAtPath($file['name'], $file['path']);
+ }
+ }
+
+ /**
+ * delete given user from plugin
+ *
+ * @param String $event name of the notification event
+ * @param User $user
+ */
+ public function deleteUser($event, $user)
+ {
+ ...
+ PageLayout::postInfo('Nutzer X aus MyPlugin gelöscht.');
+ }
+
+ /**
+ * delete data of given user from plugin
+ *
+ * @param String $event name of the notification event
+ * @param String $user_id
+ * @param String $type of data that should be removed
+ */
+ public function removeData($event, $user_id, $type)
+ {
+ switch ($type) {
+ case 'course_documents':
+ case 'personal_documents':
+ ...
+ PageLayout::postInfo('Dokumente von Nutzer X aus MyPlugin gelöscht');
+ break;
+ case 'course_contents':
+ case 'personal_contents':
+ ...
+ PageLayout::postInfo('Inhalte von Nutzer X aus MyPlugin gelöscht');
+ break;
+ case 'names':
+ ...
+ PageLayout::postInfo('Namen von Nutzer X aus MyPlugin gelöscht');
+ break;
+ case 'memberships':
+ ...
+ PageLayout::postInfo('Veranstaltungszuordnungen von Nutzer X aus MyPlugin gelöscht');
+ break;
+ }
+ }
+}
+```
+
+## Pluginmigration von Stud.IP v4.6 auf v5.0
+
+### Breaking Changes
+
+#### Geänderte API des JSUpdater
+
+Mit dem STUDIP.JSUpdater können Plugins teilhaben am regelmäßigen Pollen des Servers, wie das auch schon für Blubber oder die PersonalNotifications verwendet wird.
+
+Um im Plugin am JSUpdater teilzunehmen, gab es bisher zwei Möglichkeiten:
+
+* Aufruf von STUDIP.JSUpdater.register
+* Automatische Verwendung durch Implementation einer JS-Funktion namens "periodicalPushData"
+
+Die zweite Möglichkeit wurde entfernt und muss durch eine explizite Registrierung ersetzt werden. Alles weitere unter [Entwickler/UpdateInformation](UpdateInformation)
+
+
+# Internationalisierung von Plugins
+Die PluginEngine unterstützt eine Internationalisierung des Plugins auf Basis von gettext. Um eine Übersetzung des Plugins durchzuführen sind die üblichen Schritte nötig:
+```shell
+xgettext -n PLUGINPAKET/*.php
+```
+Übersetzen der so erzeugten messages.po
+
+```shell
+msgfmt messages.po
+```
+Dann die so erzeugte messages.mo umbenennen in gtdomain_PLUGINCLASSNAME.mo
+
+* Die .mo-Datei in die folgende Verzeichnisstruktur legen:
+ * PLUGINPAKET/locale/SPRACHKÜRZEL/LC_MESSAGES/
+* evtl. den Server neu starten, um Änderungen angezeigt zu bekommen
diff --git a/docs/docs/plugins/automatic-updates.md b/docs/docs/plugins/automatic-updates.md
new file mode 100644
index 0000000..6f43060
--- /dev/null
+++ b/docs/docs/plugins/automatic-updates.md
@@ -0,0 +1,42 @@
+---
+title: Automatische Plugin-Updates
+slug: /plugins/automatic-updates
+sidebar_label: Automatische Updates
+---
+
+### Installation
+
+Ab der Version 3.2 kann man in Stud.IP Plugins direkt von github oder anderen Repositories wie gitlab installieren. Dazu geht man als Root in die Pluginverwaltung und klickt in der Sidebar auf "Plugin von URL installieren".
+
+Es öffnet sich ein Dialogfenster, in der man die URL eingibt. Diese URL muss zum ZIP-Download auf github führen. Es reicht also nicht, die Grund-URL des Repositories anzugeben wie https://github.com/studip/PluginMarket, sondern man muss da die URL zur ZIP-Datei angeben wie https://github.com/studip/PluginMarket/archive/master.zip . So eine URL sollte es bei den meisten Systemen wie gitlab, github oder bitbucket geben.
+
+Jetzt kann man direkt auf Speichern klicken und das Plugin wird installiert.
+
+### Einrichtung
+
+Was einmal klappt, kann auch öfter klappen. Warum also sollte Stud.IP das aus dem Web installierte Plugin nicht öfter automatisch installieren und so immer auf dem neusten Stand bleiben? Genau, es spricht kaum etwas dagegen. Stud.IP wird das aber erst tun, wenn github sich bei Stud.IP meldet und Bescheid gibt, dass ein Update des Plugins vorliegt.
+
+Der Vorgang wird dann so aussehen:
+* Eine Änderung des Plugins wird ins Repository eingecheckt.
+* Das Repository meldet sich per Webhook bei Stud.IP mit der Nachricht "bei Plugin xyz hat sich was geändert".
+* Stud.IP überprüft diesen Webhook-Request, denn da könnte ja jeder kommen. Nur wenn auch wirklich das richtige Repository anruft, wird Stud.IP das auch ernst nehmen.
+* Stud.IP wird dann von sich aus die eingerichtete URL des ZIP-Downloads wieder aufrufen und das veränderte Plugin installieren.
+
+Damit das funktioniert, muss einerseits Stud.IP als auch das Repository speziell eingerichtet werden.
+
+Stud.IP braucht die URL des ZIP-Downloads und die Angabe, ob der Webhook über einen Security-Token abgesichert werden soll. Die Absicherung per Security-Token funktioniert zur Zeit nur mit github.
+
+Das Repository muss die genaue URL kennen, die vom Webhook aufgerufen werden soll. So eine URL sieht in etwa so aus:
+http://www.superstudip.de/studip/dispatch.php/plugins/trigger_automaticupdate/OnlineList?s=8d1e6b52927a7f5f567f7aedeb8b17b0
+Diese URL beinhaltet schon einen Sicherheitstoken; nur wer den Token, also die exakte URL kennt, kann überhaupt den Request aufrufen. Dazu muss gesagt werden, dass Tokens in URLs nicht besonders sicher sind. Aber sie sind besser als nichts. Und bei gitlab oder anderen Systemen ist dies zur Zeit die einzige mögliche Absicherung.
+Falls gewünscht, kann in github der Webhook noch über einen Security-Token abgesichert werden. Damit ist NICHT der Token aus der URL gemeint, sondern der separate Token, der in Stud.IP unter der URL angezeigt wird.
+
+Sind Stud.IP und das Repository gleichermaßen eingerichtet, so ist eigentlich alles getan.
+
+### Wichtig
+
+Wir empfehlen nicht, diese automatischen Updates für den Produktivbetrieb einzusetzen. Aber für Testsysteme können sie Gold wert sein. Besonders bei umfangreichen und komplizierten Plugins will man vielleicht immer die ganzen Plugins nach jeder Änderung über die Oberfläche von Stud.IP hochladen. Man bedenke, dazu muss man als Root angemeldet sein, zur Pluginverwaltung gehen, in die Sidebar klicken, runter scrollen, den Dateiupload anklicken, feststellen, dass man vergessen hat, das Plugin zu zippen, dann zippt man das Plugin, wählt nochmal die Datei aus. Und dann dauert es je nach Größe des Plugins noch quälend lange, bis das Plugin hochgeladen worden ist. Und mal ehrlich: wer hat in dem ganzen Prozedere noch nie ein fertig gezipptes Plugin mit ins Repository hochgeladen?
+
+Automatische Updates vereinfachen also das Testen mit Testservern ungemein. Man muss nur noch den Fortschritt des Plugins ins Repository pushen, was man eh machen muss, und die angeschlossenen Testsysteme aktualisieren sich alle gleichzeitig. Wenn man mehrere Testsysteme hat (bei der Entwicklung vom CampusConnect-Plugin ist das zum Beispiel absolut notwendig), verhindert das automatische Update so auch zielsicher, dass man irgendwo ein Update vergessen hat und dann Fehlern nachjagd, die im Code gar nicht existieren.
+
+Ebenso hat das automatische Update den Vorteil, dass der Entwickler des Plugins nicht notwendigerweise Root-Zugriff auf das Testsystem haben muss, um Updates einspielen zu können.
diff --git a/docs/docs/plugins/tutorial-funktion.md b/docs/docs/plugins/tutorial-funktion.md
new file mode 100644
index 0000000..20faae5
--- /dev/null
+++ b/docs/docs/plugins/tutorial-funktion.md
@@ -0,0 +1,620 @@
+Dieses Tutorial stellt den zweiten Teil eines Plugin Tutorials dar.
+Im [ersten Teil](Plugin-Tutorial-I-(Plugin-Struktur)) wurde bereits die grundlegende Plugin-Struktur erstellt
+und einige Basiskonzepte erläutert, welches beides allgemein für jedes Plugin gelten.
+Dieser Teil implementiert nun ein explizites Beispiel eines Plugins und soll nur veranschaulichen,
+wie beispielsweise gewünschte Funktionalität in einem Plugin umgesetzt werden kann.
+
+Es ist daher natürlich stark zu empfehlen, den ersten Teil nachverfolgt zu haben
+und es sollten die Basiskonzepte wie [Trails](Trails) verstanden worden sein.
+Am Ende dieser Seite ist wieder eine ZIP-Datei mit dem erstellten Code und zusätzlicher phpDoc angehangen.
+
+Nun wird der Kontext des zu implementierenden wirklich relevant:
+Auf einer Übersichtsseite sollen Nutzende alle erstellten "Texte" einsehen können.
+Sie sollen außerdem die Möglichkeit erhalten,
+Texte selber zu erstellen und ihre existierenden Texte zu bearbeiten.
+
+
+## Übersichtsseite
+Als simples Beispiel einer action inklusive ihrer view,
+erstellen wir eine Übersichtsseite auf der alle existierenden Texte in einer Tabelle angezeigt werden.
+Dazu befüllen wir die bereits im ersten Teil erstellte `index_action` und ihre `index.php`-view.
+
+### index action
+In der action-Methode laden wir also alle nötigen Daten, welche die View benötigt.
+In diesem Fall benötigen wir nur alle Text-Objekte,
+die wir mit `$this->all_texts = \TextPlugin\Text::findBySQL("1");` laden und der view zur Verfügung stellen können.
+Außerdem aktivieren wir mit `Navigation::activateItem('text_root/text_overview');` das aktuelle Navigationselement
+und setzen mit `PageLayout::setTitle($this->_('Texte Übersicht'));` den Seitentitel,
+der in der Sidebar der Seite angezeigt wird.
+
+```php
+ public function index_action()
+ {
+ Navigation::activateItem('text_root/text_overview');
+ PageLayout::setTitle($this->_('Texte Übersicht'));
+
+ $this->all_texts = \TextPlugin\Text::findBySQL("1");
+ }
+```
+
+### index view
+Die geladenen `Text`-Objekte sind nun also in der `overview/index.php` über die Variable `$all_texts` zugreifbar.
+Die view befüllen wir mit einer Tabelle, die den Titel, den Typ, den oder die Autorin und das Erstellungsdatum anzeigt.
+```html
+<table class="default sortable-table">
+ <caption>
+ <?= $_('Texte') ?>
+ </caption>
+ <colgroup>
+ <col>
+ <col style="width: 80px">
+ <col style="width: 20%">
+ <col style="width: 80px">
+ </colgroup>
+ <thead>
+ <tr>
+ <th data-sort="text"><?= $_('Titel'); ?></th>
+ <th data-sort="text"><?= $_('Typ'); ?></th>
+ <th data-sort="text"><?= $_('Autor*in'); ?></th>
+ <th data-sort="digit"><?= $_('Erstellt am'); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <? if ($all_texts): ?>
+ <? foreach ($all_texts as $text_obj) : ?>
+ <tr>
+ <td><?= htmlReady($text_obj->title); ?></td>
+ <td><?= $text_obj->getTypeDescription(); ?></td>
+ <td>
+ <a href="<?= URLHelper::getLink('dispatch.php/profile?username=' . $text_obj->author->username) ?>">
+ <?= htmlReady($text_obj->author->getFullName()) ?>
+ </a>
+ <td><?= strftime('%x', $text_obj->mkdate); ?></td>
+ </tr>
+ <? endforeach; ?>
+ <? else: ?>
+ <tr>
+ <td colspan="4">
+ <?= MessageBox::info($_('Es wurden keine Texte gefunden.')) ?>
+ </td>
+ </tr>
+ <? endif; ?>
+ </tbody>
+</table>
+
+```
+Details zur view:
+* Die Tabelle kann sortierbar gemacht werden, indem die class `sortable-table` für das `<table>` element genutzt wird
+und jeweils für die `<th>` elemente ein typ mit `data-sort` angegeben wird ([Tablesorter](Templates#Tabellen))
+* Die Strings wie `Titel` sind mit `$_($string)`als übersetzbar gekennzeichnet
+* `$all_texts` ist die aus der action-methode geladene variable und ist ein Array aus `Text`-Objekten,
+weshalb bspw. mit `$text_obj->title` der Titel des aktuellen `Text`-Objekts ausgegeben werden kann.
+Da wir in der `configure()`-Methode der Text-Modelklasse angegeben hatten,
+dass `author` ein `User`-Objekt ist,
+welches mit dem Fremdschlüssel `author_id` identifiziert wird,
+kann mit `$text_obj->author` direkt auf das `User`-Objekt zugegriffen werden
+und so der Name mit `getFullName()` ausgegeben werden (siehe [Relationen in SORM](SimpleORMap#Relationen)).
+* `$text_obj->getTypeDescription();` wird im nächsten Unterabschnitt ausführlich erläutert
+* Mit `URLHelper::getLink('dispatch.php/profile?username=' . $text_obj->author->username)` wird ein Link erstellt,
+welcher auf das Profil des Nutzers führt (siehe [URLHelper](URLHelper)).
+* Mit `strftime('%x', $text_obj->mkdate)` wird der Unix-Timestamp der in mkdate gespeichert ist,
+als Datum ausgegeben (siehe [php strftime](https://www.php.net/manual/de/function.strftime.php)).
+* Wenn keine Texte existieren, wird mit `MessageBox::info()` eine Info-Box erstellt (siehe [MessageBox](MessageBox)).
+
+### Typ eines Text-Objekts
+In der migration hatten wir den `type` eines `Text`-Objekts als `TINYINT(2) NOT NULL DEFAULT 1` festgelegt,
+sodass dort ein Integer abgespeichert ist.
+Die Idee des `type`-Feldes ist es, dass wir einen text-typen wie "Kurzgeschichte", "Roman" etc. angeben können.
+Dazu kann man ein `enum` nutzen, da wir nur vorgefertigte typen zu lassen.
+Dies würde aber bedeuten, dass wir jedes mal die Datenbank anpassen müssen,
+wenn wir bspw. einen neuen Typen hinzufügen oder einen umbenennen möchten.
+Stattdessen nutzen wir einen Integer und definieren uns die Typen im Code,
+an genau einer Steller, sodass wir minimale Änderungen durchführen müssen,
+wenn wir die verfügbaren Typen anpassen.
+
+In einer statischen `getTypes()` Methode in der `Text`-Modelklasse definieren wir alle verfügbaren Typen.
+
+```php
+public static function getTypes(): array
+{
+ return [
+ 1 => dgettext(Plugin::GETTEXT_DOMAIN, 'Kurzgeschichte'),
+ 2 => dgettext(Plugin::GETTEXT_DOMAIN, 'Roman'),
+ ];
+}
+```
+Hier kann auch beispielhaft gesehen werden, wie Strings innerhalb von Modelklassen als übersetzbar gekennzeichnet werden.
+Um den aktuellen Typen eines Texts als Klartext und nicht als Integer zu erhalten,
+fügen wir auch noch eine `getTypeDescription()`-Methode hinzu.
+
+```php
+public function getTypeDescription(): string
+{
+ return self::getTypes()[$this->type] ?? 'Unbekannter Typ!';
+}
+```
+
+Wenn wir nun einen neuen Typen hinzufügen möchten,
+können wir diesen einfach in den array innerhalb von `Text::getTypes()` ergänzen.
+Da Integers zur Identifizierung genutzt werden, werden auch Tippfehler beim Vergleichen des Typs vermieden,
+bspw. `if ($text_obj->type === 'kurzgeshcichte') [...]`.
+Das Definieren eines enums kann jedoch in einigen Fällen trotzdem sinnvoll sein,
+falls beispielsweise klar ist, dass die enums nie erweitert werden.
+
+## Sidebar
+Die Übersichtsseite zeigt nun alle Texte an,
+jedoch gibt es noch keine Möglichkeit, Texte zu erstellen.
+Dazu erstellen wir eine Aktion in der Seitenleiste (Sidebar) der Übersichtsseite,
+mit der eine neue Ansicht aufgerufen wird,
+in der dann ein Text erstellt werden soll.
+
+Im `OverviewController` erstellen wir eine neue Methode,
+die nur zum Aufbauen der Sidebar dient.
+Wie die Sidebar zu nutzen ist, ist in [Sidebar](Sidebar) erläutert.
+```php
+ private function buildSidebar()
+ {
+ $sidebar = Sidebar::Get();
+
+ $actionWidget = $sidebar->addWidget(new ActionsWidget());
+ $actionWidget->addLink(
+ $this->_('Text erstellen'),
+ $this->url_for('overview/edit_text'),
+ Icon::create('add'),
+ ['data-dialog' => true]
+ );
+ }
+```
+Wir erstellen ein neues `ActionsWidget` und geben einen Link zu einer neuen action `edit_text` an,
+die wir später erstellen und befüllen werden (siehe [Trails](Trails)).
+Als [Icon](Icon) wird ein `add`-Symbol übergeben.
+Eine Übersicht der Icons ist in [Visual-Style-Guide](Visual-Style-Guide#Icons) verfügbar.
+Außerdem soll die view zum Erstellen eines Texts in einem dialog geöffnet werden.
+Views können in Stud.IP allgemein in einem dialog geöffnet werden,
+indem `data-dialog` gesetzt wird.
+Weiteres zu Dialogen in Stud.IP ist in [ModalerDialog](ModalerDialog) einsehbar.
+Schließlich sollte noch dran gedacht werden, die neu erstellte Methode in der `index_action`-methode aufzurufen,
+damit die Sidebar auch auf der Übersichtsseite angezeigt wird.
+
+## Texte erstellen
+Nun geht es darum, die action und view zum Erstellen eines Texts zu bauen.
+
+### Form erstellen
+Um auch das nachträgliche Bearbeiten von Texten zu ermöglichen und redundanten Code zu vermeiden,
+kombinieren wir direkt das Erstellen und das Bearbeiten von Texten.
+Es kann jedoch je nach Situation und Kontext auch sinnvoll sein, dass Erstellen
+und Bearbeiten in einzelne actions und views aufzuteilen, bspw. wenn die beiden
+Sichten sich stark unterscheiden.
+Da wir beim Erstellen und Bearbeiten keinen Unterschied machen möchten,
+rufen wir auch immer `edit_text_action` auf, wenn wir einen Text erstellen möchten.
+
+In der `edit_text_action` laden wir wieder alle nötigen Daten,
+die die View benötigt.
+In diesem Fall ist das lediglich ein `Text`-Objekt.
+
+```php
+ public function edit_text_action(string $text_id = '')
+ {
+ PageLayout::setTitle($this->_('Text bearbeiten'));
+ $this->text_obj = \TextPlugin\Text::find($text_id);
+ if (!$this->text_obj) {
+ $this->text_obj = new \TextPlugin\Text();
+ }
+ }
+```
+
+Da die `edit_text_action` für das Erstellen und Bearbeiten von Texten verantwortlich ist,
+versuchen wir erst einmal einen angegebenen Text zum Bearbeiten zu finden.
+Falls keine `$text_id` angegeben wurde, erstellen wir stattdessen ein `Text`-Objekt.
+Angemerkt werden sollte, dass hier lediglich ein `Text`-Objekt erstellt wird,
+welches aber noch nicht in die Datenbank gespeichert wird.
+
+Da die `edit_text_action` im OverviewController ist,
+erstellen wir eine `edit_text.php` im Verzeichnis `views/overview`.
+Die View enthält dabei ein Text-Input für den Titel,
+eine Textarea für die Beschreibung und eine Select Tag für den Typen.
+
+```html
+<? use Studip\Button; ?>
+<form class="default collapsable" action="<?= $controller->link_for('overview/store_text', $text_obj->id) ?>"
+ method="post">
+ <?= CSRFProtection::tokenTag() ?>
+ <fieldset data-open="bd_basicsettings">
+ <legend>
+ <?= $_('Grunddaten') ?>
+ </legend>
+
+ <div>
+ <label class="required">
+ <?= $_('Titel') ?>
+ </label>
+ <input name="title" required value="<?= $text_obj->title ?>">
+ </div>
+
+ <div>
+ <label>
+ <?= $_('Beschreibung') ?>
+ </label>
+ <textarea name="description"><?= $text_obj->description ?></textarea>
+ </div>
+
+ <div>
+ <label class="required">
+ <?= $_('Text Typ') ?>
+ </label>
+ <select name="type" required>
+ <? foreach (\TextPlugin\Text::getTypes() as $type_key => $type_label): ?>
+ <option value="<?= $type_key ?>" <? if ($type_key == $text_obj->type) echo 'selected' ?>>
+ <?= $type_label ?>
+ </option>
+ <? endforeach; ?>
+ </select>
+ </div>
+
+ </fieldset>
+
+ <footer data-dialog-button>
+ <?= Button::create($_('Übernehmen')) ?>
+ </footer>
+</form>
+```
+Details zur View:
+* Mit `$controller->link_for()` erstellen wir direkt einen Link für das `<form>`-element,
+welcher auf eine `store_text_action` im OverviewController verweist,
+in der wir später die angegebenen Daten abspeichern.
+* Mit `CSRFProtection::tokenTag()` erstellen wir einen Token,
+den wir in der `store_text_action` nutzen, um "Cross-Site Request Forgery" zu verhindern ([CSRFProtection](CSRFProtection))
+* Mit `Button::create($string)` wird ein submit-button erstellt (siehe [Buttons](Buttons)).
+Der submit-button wird im `<footer>`-element mit `data-dialog-button` erstellt,
+damit er in dialog-Fenstern neben dem "Schließen"-button angezeigt wird.
+
+### Form-Daten speichern
+Schließlich müssen noch die Nutzereingaben abgespeichert werden.
+Dazu erstellen wir im OverviewController eine `store_text_action`,
+auf die die form in `edit_text.php` beim Submit bereits weiterleitet.
+
+```php
+ public function store_text_action(string $text_id = '')
+ {
+ CSRFProtection::verifyRequest();
+ $this->text_obj = \TextPlugin\Text::find($text_id);
+ if (!$this->text_obj) {
+ $this->text_obj = new \TextPlugin\Text();
+ $this->text_obj->author_id = $GLOBALS['user']->id;
+ }
+ $this->text_obj->setData([
+ 'title' => Request::get('title'),
+ 'description' => Request::get('description'),
+ 'type' => Request::int('type')
+ ]);
+
+ if ($this->text_obj->store() !== false) {
+ PageLayout::postSuccess($this->_('Der Text wurde erfolgreich gespeichert'));
+ } else {
+ PageLayout::postError($this->_('Beim Speichern des Texts ist ein Fehler aufgetreten'));
+ }
+ $this->redirect('overview/index');
+ }
+```
+Die `store_text_action` speichert auch noch nicht existierende Texte,
+daher wird wie in `edit_text_action` ein neues Objekt erstellt,
+wenn kein existierendes gefunden wird.
+Mit `setData()` werden die Nutzereingaben dem `Text`-Objekt zugewiesen.
+Auf die Nutzereingaben können über `Request` zugegriffen werden,
+wobei der angegebene Parameter mit dem html-`name` des input-elements (`<input>`, `<textarea>` etc.)
+übereinstimmen muss. In [Request](Request) sind noch weitere Informationen zu `Request` erläutert.
+Eine id für den Primärschlüssel `text_id` und das `mkdate` wird von SimpleORMap für neue Einträge automatisch erstellt
+und `chdate` wird automatisch aktualisiert.
+Die `author_id` wird jedoch nicht automatisch gesetzt,
+sodass wir sie für neue Texte einmalig eintragen.
+Je nachdem, ob das Speichern erfolgreich ist,
+geben wir auf der nächsten Seite eine Erfolgs- oder Fehlermeldung an (siehe [PageLayout](PageLayout)).
+Das Speichern eines Texts benötigt keine View,
+wir möchten lediglich die Daten abspeichern und dann wieder die Übersichtsseite aufrufen,
+weshalb wir mit `$this->redirect('overview/index');` auf `index_action` umleiten
+und auch keine `store_text.php`-view Datei benötigen.
+
+### Bearbeiten ermöglichen
+Texte sind technisch nun bearbeitbar,
+jedoch gibt es für Nutzende noch keine Möglichkeit,
+die Bearbeitungsansicht für existierende Texte aufzurufen.
+Dafür fügen wir der Übersichtstabelle in `overview/index.php` eine weitere Spalte `Aktion` hinzu,
+in der wir Aktionen für explizite Texte ermöglichen wie bspw. eine Detailansicht, das Löschen oder halt das Bearbeiten.
+
+In der `<colgroup>` ergänzen wir `<col style="width: 40px">`.
+Im `<thead>` fügen wir `<th data-sort="false"><?= $_('Aktion'); ?></th>` hinzu.
+Um Aktionen einzufügen, sollte das [ActionMenu](ActionMenu) genutzt werden,
+welches sehr ähnlich zu `ActionsWidget` funktioniert.
+Wir erstellen also ein neues `ActionMenu` innerhalb des `<tbody>` und übergeben einen Link zur `edit_text_action`.
+```php
+<td>
+ <? $actions = ActionMenu::get(); ?>
+ <? $actions->addLink(
+ $controller->url_for('overview/edit_text/' . $text_obj->id),
+ $controller->_('Bearbeiten'),
+ Icon::create('edit'),
+ ['data-dialog' => true]
+ ); ?>
+ <?= $actions ?>
+</td>
+
+```
+Wichtig hierbei ist, dass wir auch die id des jeweiligen `Text`-Objekt übergeben,
+sodass kein neues Objekt erstellt wird, sondern das existierende bearbeitet werden kann.
+Da `ActionMenu` die Methode `__toString` zum rendern nutzt,
+können wir das erstellte Menü auch einfach mit `<?= $actions ?>` rendern.
+
+### Erlaubnis für Objekte
+Was wir nun erreicht haben, ist das alle Nutzende, alle Texte bearbeiten können.
+Wir sollten also sicherstellen, dass nur berechtigte Nutzer die Texte bearbeiten können.
+Für ein `Text`-Objekt legen wir fest, dass nur der Ersteller und root-Nutzer das Objekt bearbeiten dürfen.
+Dafür erstellen wir eine `hasPermission`-methode in `models/Text.php`, in der wir genau dies sicherstellen.
+
+```php
+public function hasPermission(): bool
+{
+ return $GLOBALS['user']->id === $this->author_id || $GLOBALS['user']->perms === 'root';
+}
+```
+Mit `$GLOBALS['user']` lässt sich auf das `User`-Objekt vom aktuellen Nutzenden zugreifen,
+sodass so überprüft werden kann, ob es sich um den oder die Autor*in oder einen `root`-User handelt.
+Allgemein ist es sinnvoll root-Usern alle Berechtigungen zu geben,
+das erleichtert die Entwicklung in Testumgebungen, da sich nicht ständig umgeloggt werden muss,
+und hilft in Produktivsystemen den System-Administrationen,
+da Sie dann Feedback von Nutzenden besser nachverfolgen können.
+
+`hasPermission` müssen wir nun überall überprüfen,
+wo kritische Aktionen wie das Bearbeiten oder Löschen von Texten geschieht.
+In unserem Fall ist dies in der `store_text_action` des OverviewControllers.
+Wir überprüfen also, direkt nachdem wir das `Text`-Objekt geladen haben,
+ob der Nutzer das Objekt speichern darf.
+Wenn keine Berechtigung vorhanden ist,
+geben wir einen Fehler aus und leiten auf die Übersichtsseite um.
+
+```php
+ public function store_text_action(string $text_id = '')
+ {
+ CSRFProtection::verifyRequest();
+ $this->text_obj = \TextPlugin\Text::find($text_id);
+ if (!$this->text_obj) {
+ $this->text_obj = new \TextPlugin\Text();
+ $this->text_obj->author_id = $GLOBALS['user']->id;
+ }
+ if (!$this->text_obj->hasPermission()) {
+ PageLayout::postError($this->_('Sie haben keine Berechtigung dazu, den Text anzupassen'));
+ $this->redirect('overview/index');
+ return;
+ }
+ $this->text_obj->setData([
+ [...]
+ }
+```
+
+Wir haben nun das notwendigste getan, um das unerlaubte Ändern von Texten zu verhindern.
+Wir sollten aber auch verhindern, dass Nutzende die Bearbeitungsseite überhaupt aufrufen können,
+damit Ihnen nicht suggeriert wird, dass Sie Texte bearbeiten können,
+nur um Ihnen dann eine Fehlermeldung anzuzeigen.
+
+Dafür fügen wir in der `overview/index.php` die `edit_text_action` nur hinzu,
+wenn der aktuelle Nutzer die jeweilige Berechtigung hat.
+Effektiv fügen wir also nur eine if-Abfrage der `hasPermission` Methode vor dem Hinzufügen des Links hinzu.
+
+```php
+ <? $actions = ActionMenu::get(); ?>
+ <? if ($text_obj->hasPermission()): ?>
+ <? $actions->addLink(
+ $controller->url_for('overview/edit_text/' . $text_obj->id),
+ $controller->_('Bearbeiten'),
+ Icon::create('edit'),
+ ['data-dialog' => true]
+ ); ?>
+ <? endif; ?>
+ <?= $actions ?>
+```
+Da Nutzende die `edit_text_action` noch mit dem Manipulieren der Browser-URL aufrufen können,
+überprüfen wir in der `edit_text_action`-methode wie in der `store_text_action`-methode die Berechtigung des Nutzers
+und geben ggf. einen Fehler aus. Da wir nun verhindern, dass Nutzende auf legitimen Wege die action aufrufen,
+können wir statt einen Fehler anzuzeigen auch eine Exception werfen,
+sowohl in `edit_text_action`, als auch `store_text_action`.
+
+```php
+public function edit_text_action(string $text_id = '')
+{
+ PageLayout::setTitle($this->_('Text bearbeiten'));
+ $this->text_obj = \TextPlugin\Text::find($text_id);
+ if (!$this->text_obj) {
+ $this->text_obj = new \TextPlugin\Text();
+ $this->text_obj->author_id = $GLOBALS['user']->id;
+ }
+ if (!$this->text_obj->hasPermission()) {
+ throw new AccessDeniedException($this->_('Sie haben keine Berechtigung dazu, den Text anzupassen'));
+ }
+}
+```
+
+Da wir wissen, dass für `hasPermission` die `author_id` gesetzt sein muss,
+setzen wir wie in der `store_text_action` die `author_id`,
+wenn wir ein neues `Text`-Objekt erstellen.
+Das heißt, dass wir eigentlich immer bevor wir `hasPermission` aufrufen,
+sicher gehen sollten, dass eine `author_id` gesetzt ist.
+Da dies sehr Fehleranfällig ist, sollte stattdessen festlegen werden,
+dass beim Erstellen eines neuen `Text`-Objekts die `author_id` automatisch gesetzt wird.
+Dies würde man am sinnvollsten im `after_initialize`-callback innerhalb der `configure()`-Methode
+der `Text.php`-Modelklasse festlegen.
+Wie `after_initialize`-callbacks und andere Model-spezifischen callbacks wie `after_store`, `before_delete`
+programmiert werden, kann in [SimpleORMap](SimpleORMap) nachgelesen werden.
+
+## Weitere Funktionalitäten
+Die Grundfunktionalität sind nun vorhanden.
+Nutzer können alle Texte erstellen, bearbeiten und einsehen.
+In diesem Abschnitt werden noch Beispiele für weitere Funktionalitäten aufgeführt und erläutert,
+die bei Interesse oder Bedarf zusätzlich betrachtet werden können.
+
+Im Folgenden eine kurze Übersicht zu den Unterkapiteln:
+* `I18n` ermöglicht, dass Nutzer Texte in mehreren Sprachen eingeben können.
+Die Ansicht zum Bearbeiten von Texten wird also mithilfe von `I18n` so angepasst,
+dass Nutzende den Titel und die Beschreibung von Texten in mehreren Sprachen angeben können.
+Die Texte werden dann in der jeweiligen Sprache angezeigt,
+die die jeweiligen Nutzenden in Stud.IP eingestellt haben.
+* `wysiwyg` ist ein ausführlicher Text-Editor und ermöglicht Nutzenden,
+die Formatierung von Texten.
+In diesem Unterabschnitt wird das Beschreibungsfeld eines Textes zu einem wysiwyg Feld umgebaut,
+sodass Nutzende ihre Texte mit verschiedenen Formatierungen anpassen können.
+* Wir möchten eine Detail-Seite für Texte einführen,
+in der alle Nutzenden Details zu jeweiligen Texten einsehen können.
+Diese Funktion wird vor allem wichtig,
+wenn Texte lange Beschreibungen haben oder zukünftig mehr Attribute erhalten,
+da die Tabelle auf der Übersichtsseite dann unübersichtlich wird.
+Dieser Abschnitt ist jedoch allgemein relativ simpel
+und ist sicherlich auch ohne Erläuterung mit dem jetzigen Wissen selbst erstellbar.
+* Auf der Übersichtsseite soll das Filtern von Texten ermöglicht werden,
+indem in der Sidebar nach Titel und Beschreibung gesucht werden kann.
+* Da eventuell sehr viele Texte auf der Übersichtsseite angezeigt werden könnte,
+da ja schließlich alle geladen werden, soll eine Pagination hinzugefügt werden,
+um immer nur eine feste Anzahl an Texten pro Seite zu laden und die Ladezeit damit zu verkürzen.
+
+### I18n
+[gettext](Howto/Internationalisierung#internationalisierung-im-php-code) wird dazu genutzt,
+String die von den Entwicklern fest implementiert werden auf andere Sprachen zu übersetzen.
+I18n bietet im Gegensatz dazu den Nutzenden die Möglichkeit,
+Texte die sie angeben, in verschiedenen Sprachen anzugeben.
+Die Texte werden dann je nach der jeweiligen eingestellten Sprache der Nutzenden ausgegeben.
+
+Im TextPlugin sollen Nutzende die Möglichkeit erhalten,
+den Titel des Texts und die Beschreibung auch in mehreren Sprachen anzugeben.
+Wie in [I18n](Howto/Internationalisierung#i18n) sollten dafür drei Punkte beachtet werden
+
+#### Feld als i18n_field kennzeichnen
+Die jeweiligen Felder sollten als `i18n_field` gekennzeichnet werden.
+Wir fügen also in der `configure()`-Methode der `Text`-Modelklasse folgendes hinzu
+```php
+$config['i18n_fields']['title'] = true;
+$config['i18n_fields']['description'] = true;
+```
+
+#### I18n Inputs nutzen
+Es müssen statt html-Input-Elementen entsprechende I18n-Input-Elemente genutzt werden.
+Wir passen also das `<input>` und das `<textarea>`-Element in der `edit_text.php`-view dementsprechend an.
+```php
+<div>
+ <label class="required">
+ <?= $_('Titel') ?>
+ </label>
+ <?= I18N::textarea('title', $text_obj->title, ['required' => true]) ?>
+</div>
+
+<div>
+ <label>
+ <?= $_('Beschreibung') ?>
+ </label>
+ <?= I18N::textarea('description', $text_obj->description) ?>
+</div>
+```
+#### Request::i18n
+Für `I18n`-Felder muss `Request::i18n()` zum Erhalten der jeweiligen form-Daten ausgeführt werden.
+In unserem Plugin betrifft das nur die `store_text_action` im `OverviewController`.
+```php
+$this->text_obj->setData([
+ 'title' => Request::i18n('title'),
+ 'description' => Request::i18n('description'),
+ 'type' => Request::int('type')
+]);
+```
+
+### wysiwyg
+Nutzende sollen außerdem verschiedene Formatierungsoptionen für die Beschreibung eines Texts erhalten.
+In Stud.IP wird dies mithilfe des [Wysiwyg](Wysiwyg)-Editors erreicht.
+Da Nutzende diesen aber in Stud.IP auch abstellen können, bieten wir alternativ auch eine simplere toolbar an.
+
+Dazu geben wir in die `<textarea>` der Beschreibung in `edit_text.php` als html-class `wysiwyg add_toolbar` an.
+```php
+<?= I18N::textarea('description', $text_obj->description, ['class' => 'wysiwyg add_toolbar']) ?>
+```
+bzw. ohne i18n-feld
+```html
+<textarea name="description" class="wysiwyg add_toolbar"><?= $text_obj->description ?></textarea>
+```
+
+Wenn die Beschreibung ausgegeben wird, sollte daran gedacht werden `formatReady()` statt `htmlReady()` zu verwenden.
+
+### Detail-Ansicht
+Als weiteres Beispiel einer action mit view wird eine Detail-Ansicht für Texte hinzugefügt,
+in der deutlich mehr Informationen angezeigt werden können, als in der Übersichtstabelle.
+
+Dazu fügen wir in das `ActionMenu` der `index.php` einen weiten Link zu der neuen `view_text_action` hinzu.
+```php
+<? $actions = ActionMenu::get(); ?>
+<? $actions->addLink(
+ $controller->url_for('overview/view_text/' . $text_obj->id),
+ $controller->_('Ansehen'),
+ Icon::create('log'),
+ ['data-dialog' => true]
+); ?>
+<? if ($text_obj->hasPermission()): ?>
+ [...]
+<? endif; ?>
+<?= $actions ?>
+```
+Da erstmal alle Nutzenden die Detailansicht aufrufen können, fragen wir hier nicht `hasPermission` ab.
+In den `OverviewController` erstellen wir dann die `view_text_action`.
+```php
+public function view_text_action(string $text_id)
+{
+ PageLayout::setTitle($this->_('Text ansehen'));
+ $this->text_obj = \TextPlugin\Text::find($text_id);
+ if (!$this->text_obj) {
+ throw new InvalidArgumentException($this->_('Der angefragte Text konnte nicht gefunden werden'));
+ }
+}
+```
+
+Schließlich erstellen wir noch die `view_text`-view, in der einfach alle gewünschten Attributen ausgegeben werden.
+```html
+<table class="default">
+ <colgroup>
+ <col style="width: 40%">
+ </colgroup>
+ <tbody>
+ <tr>
+ <td><strong><?= $_('Titel') ?></strong></td>
+ <td><?= htmlReady($text_obj->title) ?></td>
+ </tr>
+ <tr>
+ <td><strong><?= $_('Beschreibung') ?></strong></td>
+ <td><?= formatReady($text_obj->description) ?></td>
+ </tr>
+ <tr>
+ <td><strong><?= $_('Typ') ?></strong></td>
+ <td><?= $text_obj->getTypeDescription() ?></td>
+ </tr>
+ <tr>
+ <td><strong><?= $_('Autor') ?></strong></td>
+ <td><?= htmlReady($text_obj->author->getFullname()) ?></td>
+ </tr>
+ <tr>
+ <td><strong><?= $_('Erstellt am') ?></strong></td>
+ <td><?= strftime('%x', $text_obj->mkdate) ?></td>
+ </tr>
+ <tr>
+ <td><strong><?= $_('Letzte Änderung am') ?></strong></td>
+ <td><?= strftime('%x', $text_obj->chdate) ?></td>
+ </tr>
+ </tbody>
+</table>
+```
+Falls für die Beschreibung der `wysywig`-Editor zur Verfügung gestellt wurde,
+sollte hier darauf geachtet werden `formatReady()` statt `htmlReady()` zu nutzen.
+
+### Texte suchen und filtern
+[TODO]
+
+### Pagination
+[TODO]
+
+## Zusammenfassung
+In diesem Teil wurde
+* Eine Übersichtsseite für alle Texte mit entsprechender action und view erstellt ([Trails](Trails))
+* Ein Sidebar-Widget erstellt ([Sidebar](Sidebar))
+* Eine action und view zum Bearbeiten und eine action zum Speichern der form-Daten erstellt
+
+Der komplette bisher erstellte Code zuzüglich phpDoc ist hier ([TextPlugin.zip](../assets/0e34a84cb91701dda04150e39bd3067f/TextPlugin.zip)) als ZIP-Datei verfügbar.
diff --git a/docs/docs/plugins/tutorial-struktur.md b/docs/docs/plugins/tutorial-struktur.md
new file mode 100644
index 0000000..271e217
--- /dev/null
+++ b/docs/docs/plugins/tutorial-struktur.md
@@ -0,0 +1,588 @@
+Dieses Tutorial dient zum ersten Kontakt mit Stud.IP Plugins.
+Es wird beispielhaft ein Stud.IP Plugin erstellt,
+um aufzuzeigen, welche Komponenten für ein Stud.IP Plugin relevant sind.
+Dabei werden die einzelnen Komponenten nicht ausführlich noch einmal erklärt,
+stattdessen werden auf ausführliche Erklärung im Wiki verwiesen, die sich vorher angeschaut werden sollten.
+Dieses Tutorial stellt somit eine Art Leitfaden dar, der verfolgt werden kann,
+um die verschiedenen Stud.IP Komponenten kennenzulernen und in einen sinnvollen Zusammenhang zu bringen.
+
+Diese Seite stellt dabei den ersten Teil des Tutorials dar,
+indem erst einmal die Grundstruktur eines Plugins erklärt und erstellt wird.
+Dieser Teil ist somit größtenteils unabhängig von der eigentlichen Funktionalität des Plugins
+und kümmert sich nur darum eine Basis zu Erstellen,
+mit der angenehm Plugins erstellt und weiterentwickelt werden.
+Ein wenig Kontext für die Funktionalitäten des Plugins ist aber notwendig und fürs Verständnis auch sinnvoll.
+
+Als Ziel soll das Plugin beliebigen Stud.IP Nutzern das Erstellen von "Texten" ermöglichen.
+"Texte" bestehen dabei erst einmal nur aus einem Titel, einem Inhalt und besitzen einen Typ.
+Außerdem sollen die Nutzer eine Übersicht über alle erstellten Texte einsehen können.
+
+Am Ende dieser Seite ist eine ZIP-Datei mit dem bisherigen Code und zusätzlicher phpDoc angehangen,
+damit der eigene Fortschritt überprüft werden kann.
+Es sollte jedoch versucht werden, dass PluginTutorial eigenständig nachzuverfolgen.
+
+Um dieses Tutorial vernünftig mitverfolgen zu können,
+wird eine laufende Stud.IP Testumgebung (mind. Stud.IP 4.6) inklusive Webserver, php und Datenbanksystem benötigt.
+
+
+## Studip-Coding Style
+Um das Arbeiten mit mehreren Entwicklern zu erleichtern,
+sollte ein einheitlicher Coding Style angestrebt werden.
+Der Coding Style für Stud.IP ist [hier](CodingStyle) zu finden.
+Als zweite untergestellte Quelle kann [dieser](https://www.php-fig.org/psr/psr-12/) Artikel herangezogen werden.
+
+## Grundgerüst
+Damit ein Verzeichnis von Stud.IP als ein Plugin erkannt wird,
+sind in der Wurzel des Verzeichnisses zwei Dateien notwendig.
+Eine `plugin.manifest` Datei, welches Meta-Daten über das Plugin enthält
+wie z.B. den Pluginnamen oder die aktuelle Versionsnummer des Plugin, und eine Plugin-Klasse,
+welche als initiale Instanz von Stud.IP aufgerufen wird.
+
+### Plugin manifest
+Welche Meta-Daten im `plugin.manifest` hinterlegt werden können,
+ist in [Plugin-Manifest](PluginSchnittstelle#plugin-manifest) ersichtlich.
+Für unser Beispiel-Plugin würde das `plugin.manifest` folgendermaßen aussehen:
+```
+pluginname=TextPlugin
+pluginclassname=TextPlugin
+origin=UOL
+version=1.0
+studipMinVersion=4.6
+```
+### Haupt-Plugin-Klasse
+Diese Datei beinhaltet die Plugin-Klasse. Sie muss den Klassennamen tragen,
+der in `plugin.manifest` mit `pluginclassname` festgelegt wurde.
+Der Dateiname muss identisch mit dem Klassennamen sein.
+Im Beispiel muss also der Dateiname `TextPlugin.class.php` lauten und darin eine Klasse `TextPlugin` enthalten sein.
+Die Plugin-Klasse erbt standardmäßig von der `StudipPlugin`-Klasse und implementiert ein oder mehrere Plugin-Interfaces.
+Die verschiedenen Typen von Plugins sind in [Plugin-Interfaces](PluginSchnittstelle#plugin-interfaces) erläutert.
+Da das TextPlugin systemweit erreichbar sein soll, implementiert es die `SystemPlugin` Schnittstelle.
+Die Plugin-Klasse sieht dann folgendermaßen aus:
+```php
+<?php
+
+class TextPlugin extends StudIPPlugin implements SystemPlugin
+{
+
+}
+```
+
+### Installation
+Da das Plugin nun so weit ist, dass es von Stud.IP als Plugin erkannt wird, kann es nun installiert werden.
+Dafür gibt es im Wesentlichen zwei Möglichkeiten: Man kann das Plugin zu einer `.zip`-Datei komprimieren
+und direkt in Stud.IP installieren oder falls es ein Repository für das Plugin gibt,
+kann das Plugin auch zuerst geklont werden und dann in Stud.IP eingebunden werden.
+
+Installierte Plugins sind unabhängig davon, wie sie installiert wurden,
+im Studip-Verzeichnis unter `public/plugin_packages/<origin>` zu finden,
+wobei `<origin>` jenes origin ist, welches im jeweiligen `plugin.manifest` angegeben ist.
+
+#### Plugin als ZIP-Datei installieren
+Um das Plugin als `.zip`-Datei direkt zu installieren,
+muss als aktiver `root`-User nach `Admin` => `System` => `Plugins` navigiert werden.
+Links in der Sidebar kann dann die `.zip`-Datei ausgewählt oder per Drag and Drop installiert werden.
+
+Wenn das Plugin installiert ist, muss es schließlich noch aktiviert werden.
+Dafür muss lediglich in der gleichen Ansicht das Plugin mit der "Aktiv" checkbox aktiviert
+und die Änderung ganz unten auf der Seite gespeichert werden.
+
+
+#### Plugin aus einem Repo installieren
+Falls für das Plugin ein Repository existiert,
+kann das Repository auch geklont werden und das Plugin dann installiert werden.
+Mit dem Repository kann dann ganz normal gearbeitet werden.
+
+Dazu muss das Repository des Plugins in das entsprechende Verzeichnis geklont werden.
+Im Falle des TextPlugins muss das Repo also in `public/plugin_packages/UOL` geklont werden,
+da der `origin` im `plugin.manifest` als `UOL` festgelegt ist.
+Dabei sollte außerdem darauf geachtet werden, dass der Repository-Pfadname, der gleiche ist,
+wie der festgelegte `pluginname`.
+Insgesamt würde das Stud.IP also dann folgendermaßen aussehen:
+```ini
+<studip-verzeichnis>
+ public\
+ plugin_packages\
+ core\
+ UOL\
+ TextPlugin\
+ .git
+ plugin.manifest
+ TextPlugin.class.php
+```
+Als `root`-User muss dann auch hier nach `Admin` => `System` => `Plugins` navigiert werden
+und links in der Sidebar unter "Ansichten" die Ansicht "Vorhandene Plugins registrieren" gewählt werden.
+Das TextPlugin sollte nun hier als Installationsmöglichkeit gelistet sein und installiert werden.
+
+Wie beim Installieren des Plugins als ZIP-Datei
+muss das Plugin nach dem Installieren noch unter `Admin` => `System` => `Plugins`aktiviert werden.
+
+#### Weitere Arbeit mit dem Plugin
+Da das Plugin nun installiert ist,
+können alle folgenden Änderungen direkt im Verzeichnis des Plugins erfolgen
+und werden von Stud.IP automatisch erkannt.
+Das Plugin muss und sollte somit nicht bei jeder Änderung neu installiert werden.
+
+## Inhalt des Plugins
+Da nun das Plugin installiert und aktiviert ist,
+kann sich um die eigentliche Funktionalität des Plugins gekümmert werden.
+Wir möchten eine Übersichtsseite zum Anzeigen aller Texte als initiale Anlaufstelle des Plugins erstellen.
+
+### Navigation
+Dazu muss nun erstmal eine Navigation erstellt werden, um auf diese Seite navigieren zu können.
+Wie die Navigation in Stud.IP funktioniert ist in [Navigation](Navigation) erläutert.
+Da die Navigation zur Übersichtsseite immer erstellt werden soll,
+wird die Navigation in der `__construct`-Methode des TextPlugins erstellt.
+
+```php
+ public function __construct()
+ {
+ parent::__construct();
+
+ $root_nav = new Navigation('Texte', PluginEngine::getURL($this, [], 'overview'));
+ $root_nav->setImage(Icon::create('file-text', Icon::ROLE_NAVIGATION));
+ Navigation::addItem('/text_root', $root_nav);
+
+ $navigation = new Navigation('Übersicht', PluginEngine::getURL($this, [], 'overview'));
+ $root_nav->addSubNavigation('text_overview', $navigation);
+ }
+```
+Den initialen Reiter des Plugins nennen wir einfach `Texte`
+und hängen das Navigationselement an die Wurzel der Navigation an,
+damit es im Hauptnavigationsreiter auftaucht.
+Alle weiteren Navigationspunkte hängen wir dann an dieses Navigationselement an.
+Bisher haben wir nur eine Übersichtsseite geplant,
+sodass wir ein weiteres Navigationselement `Übersicht` erstellen,
+welches wir an unsere `Texte`-Navigation anhängen.
+Der Hauptnavigationspunkt taucht jetzt bereits in Stud.IP auf,
+jedoch wird bisher noch auf eine nicht existierende Seite verlinkt.
+
+### Plugin- und Controller-Klassen
+Bevor wir die fehlende Seite ergänzen,
+nehmen wir unserem zukünftigen Ich ein wenig Arbeit ab.
+Ein Plugin-Verzeichnis kann im Allgemeinen mehrere Plugins beinhalten
+und wird oft mehrere Controller-Klassen beinhalten.
+Wir werden später noch Code schreiben,
+die alle unsere Plugin- und Controller-Klassen benötigen werden
+und um redundanten Code zu vermeiden,
+erstellen wir jeweils eine Klasse,
+von der dann alle unsere Plugin- und Controller-Klassen erben können.
+
+Die beiden Dateien `Plugin.php` und `Controller.php` erstellen wir in einem neuen `classes`-Verzeichnis.
+
+```php
+<?php
+
+namespace TextPlugin;
+
+use StudIPPlugin;
+
+class Plugin extends StudIPPlugin
+{
+
+}
+```
+```php
+<?php
+
+namespace TextPlugin;
+
+use PluginController;
+
+class Controller extends PluginController
+{
+
+}
+```
+
+Außerdem setzen wir einen `namespace` für beide Klassen,
+um sie von anderen gleichnamigen Klassen unterscheiden zu können.
+Wir sollten auch daran denken,
+dass nun unser `TextPlugin`-Klasse von `\TextPlugin\Plugin` erben sollte,
+und nicht mehr von `StudIPPlugin`.
+
+
+### Autoload
+Dateien im Plugin-Verzeichnis werden in der Regel von Stud.IP nicht automatisch geladen.
+Für unsere neuen Klassen im `classes`-Verzeichnis müssen wir also Stud.IP explizit sagen,
+dass er die Klassen mit unserem Plugin laden soll, damit wir sie auch nutzen können.
+
+Das Laden von anderen Klassen wird in der Regel in eine `bootstrap.inc.php`-Datei ausgelagert,
+die dann vom Plugin immer mit `require_once` geladen wird.
+Wir erstellen also in der Wurzel des Plugin-Verzeichnisses eine `bootstrap.inc.php`-Datei,
+in der wir mit dem `StudipAutoloader` alle Dateien im Verzeichnis `models` laden.
+Als prefix für den autoloader sollte der `namespace` der Klassen angegeben werden.
+
+```php
+<?php
+
+StudipAutoloader::addAutoloadPath(__DIR__ . '/classes', 'TextPlugin');
+```
+
+Die Datei wird dann in der `TextePlugin.class.php` mit `require_once __DIR__ . '/bootstrap.inc.php';` eingebunden,
+vorzugsweise vor der Klassendefinition.
+
+### Trails
+Nun sind wir endlich so weit,
+dass wir uns um die Übersichtsseite kümmern können.
+[Trails](Trails) ist das Model-View-Controller Framework von Stud.IP und legt unter anderem fest,
+welche Seite bei welcher URL aufgerufen wird.
+
+Allgemein beinhaltet eine URL für ein Plugin immer `plugins.php/<pluginname>/<controller-name>/<actions-name>`.
+Der `pluginname` ist in der `plugin.manifest` festgelegt.
+Der `controller-name` ist der Dateiname des Controllers.
+Wenn die Controller-Datei `overview.php` heißt,
+muss die Klasse in der Datei `OverviewController` heißen.
+Der `action-name` ist der name einer "action", also einer Methode innerhalb des Controllers,
+die mit `_action` endet.
+
+Die URL `plugins.php/textplugin/overview/index` würde beispielsweise eine Methode `index_action()` im Controller `overview`
+im Plugin `TextPlugin` aufrufen.
+Die action `index` wird dabei immer aufgerufen,
+wenn kein `action-name` in der URL angegeben ist.
+Da wir in unserer Navigation mit `PluginEngine::getURL($this, [], 'overview')` auf einen overview Controller innerhalb unseres Plugins verweisen
+und keine action angegeben haben, sollten wir einen Controller in einem neuen Verzeichnis `controllers` namens `overview.php`
+erstellen, der die Methode `index_action` beinhaltet.
+
+```php
+<?php
+
+class OverviewController extends \TextPlugin\Controller
+{
+ public function index_action()
+ {
+
+ }
+}
+```
+
+Alle weiteren Angaben in der URL werden jeweils als Parameter in die actions reingegeben.
+Wenn im OverviewController also eine action `test_action($param1, $param2)` existiert
+und die URL `plugins.php/textplugin/overview/test/hallo/welt` aufgerufen wird,
+enthält `$param1` den string `hallo` und `$param2` den string `welt`.
+Da `/` zum Separieren in der URL genutzt wird, sollte daher auch vermieden werden,
+strings die `/` enthalten, als Parameter zu übergeben.
+
+Nachdem Trails die jeweilige action-methode aufgerufen hat und sie durchgelaufen ist,
+wird eine view gerendert, die sich auch aus der URL ergibt.
+Dabei muss es in einem `views` Verzeichnis innerhalb des Plugins ein Verzeichnis existieren,
+welches nach dem Controller benannt ist
+und innerhalb dieses Verzeichnis eine `.php` Datei die nach der action benannt ist.
+Controller-Klassen im `controllers`-Verzeichnis und view-Dateien im `views`-Verzeichnis
+werden von Stud.IP automatisch geladen,
+sodass wir sie **nicht** mit dem autoloader laden müssen.
+
+Wenn sowohl der Controller mit der action-methode als auch die passende view erstellt wurde
+und die Namenskonvention dabei eingehalten wurde,
+sollte Stud.IP nun eine leere Seite auf der TextPlugin Übersichtsseite anzeigen.
+Die Dateistruktur für das Plugin sollte bis hierhin wie folgt aussehen:
+
+```ini
+TextPlugin\
+ classes\
+ Controller.php
+ Plugin.php
+ controllers\
+ overview.php
+ views\
+ overview\
+ index.php
+ bootstrap.inc.php
+ plugin.manifest
+ TextPlugin.class.php
+```
+
+### Datenbanktabellen erstellen (Migration)
+Bevor die Seite mit vernünftigem Inhalt gefüllt werden kann,
+müssen noch die entsprechenden Datenbanktabellen erstellt werden.
+Dies geschieht in Stud.IP mittels [Migrationen](Migrations).
+
+Für die erste Version des Plugins benötigen wir nur eine Tabelle, um die erstellten Texte zu speichern.
+Die Migrationsdatei erstellen wir in einem neuen Verzeichnis `migrations` und nennen sie `01_init_texte.php`.
+Die Klasse innerhalb der Datei muss dementsprechend `InitTexte` heißen.
+
+```php
+<?php
+
+class InitTexte extends Migration
+{
+
+ public function up()
+ {
+ $db = DBManager::get();
+
+ $query = "CREATE TABLE IF NOT EXISTS tp_texte (
+ text_id CHAR(32) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
+ title TEXT NOT NULL,
+ description TEXT NULL DEFAULT NULL,
+ type TINYINT(2) NOT NULL DEFAULT 1,
+ mkdate INT(11) NOT NULL,
+ chdate INT(11) NOT NULL,
+ author_id CHAR(32) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
+ PRIMARY KEY (text_id)
+ )";
+ $db->exec($query);
+ }
+
+ public function down()
+ {
+ // drop new tables
+ DBManager::get()->exec("DROP TABLE IF EXISTS tp_texte");
+ }
+}
+```
+
+Neue Migrationen werden von Stud.IP zwar auch wie die andere Dateien automatisch erkannt,
+jedoch müssen Migrationen explizit durchgeführt bzw. installiert werden.
+Migrationen können als root-User unter `Admin` => `System` => `Plugins` in der Spalte `Schema` ausgeführt werden.
+In dieser Spalte wird standardmäßig die aktuelle migrations-version angegeben.
+In unserem Fall, da wir noch keine migration durchgeführt haben, ist die Version 0.
+Falls Stud.IP neue Migrationsdateien im Plugin-Verzeichnis erkennt,
+wird in der Spalte ein Icon angezeigt, mit dem alle neue Migrationen ausgeführt werden können.
+Wie auf der [Migrationen](Migrations) Wiki-Seite erläutert,
+stellt die Nummer im Namen der Migrationsdatei die Version der Migration dar,
+sodass neue Migrationen mit aufsteigenden Nummern zu versehen sind.
+
+
+### Model-Klassen
+Damit wir einfache SQL-Anfragen nicht selber schreiben müssen und Einträge der Tabelle als php-Objekte nutzen können,
+erstellen wir für jede Tabelle eine [SimpleORMap](SimpleORMap)-Klasse in einem neuen Verzeichnis `models`.
+Den Model-Klassen geben wir außerdem auch den `namespace` `TextPlugin`.
+Da wir nur eine Tabelle haben, erstellen wir eine Modelklasse `Text.php`:
+
+```php
+<?php
+
+namespace TextPlugin;
+
+use SimpleORMap;
+use User;
+
+class Text extends SimpleORMap
+
+{
+ protected static function configure($config = [])
+ {
+ $config['db_table'] = 'tp_texte';
+
+ $config['belongs_to']['author'] = [
+ 'class_name' => User::class,
+ 'foreign_key' => 'author_id',
+ 'assoc_foreign_key' => 'user_id'
+ ];
+
+ parent::configure($config);
+ }
+}
+```
+
+Damit wir die Modelklassen nutzen können, müssen wir wie bei `classes` dran denken,
+sie in der `bootstrap.inc.php` mit `StudipAutoloader::addAutoloadPath(__DIR__ . '/models', 'TextPlugin');` zu laden.
+Das Plugin-Verzeichnis sieht zu diesem Punkt folgendermaßen aus:
+```ini
+TextPlugin\
+ classes\
+ Controller.php
+ Plugin.php
+ controllers\
+ overview.php
+ migrations\
+ 01_init_texte.php
+ models\
+ Text.php
+ views\
+ overview\
+ index.php
+ bootstrap.inc.php
+ plugin.manifest
+ TextPlugin.class.php
+```
+
+### javascript- und css-Dateien
+Wenn css und/oder javascript Dateien genutzt werden sollen,
+sollten diese in einem neuen Verzeichnis `assets` abgelegt werden.
+Um die Dateien dann zu laden,
+kann entweder in der Plugin-Klasse `$this->addStylesheet('<css-dateipfad>');`
+bzw. `$this->addScript(<js-dateipfad>');`
+oder in Controller-Klassen `PageLayout::addStylesheet($this->plugin->getPluginURL() . '/<css-dateipfad>');`
+bzw. `PageLayout::addScript($this->plugin->getPluginURL() . '/<js-dateipfad>');` aufgerufen werden.
+
+
+### Lokalisierung
+Da Stud.IP nicht nur von deutschsprachigen Nutzern genutzt wird,
+sollte das Plugin auch auf andere Sprachen übersetzbar sein.
+Wie in [Internationalisierung](Howto/Internationalisierung) erklärt ist,
+geschieht dies in Stud.IP mithilfe des `gettext`-Packets.
+
+#### Texte als Übersetzbar kennzeichnen
+Da aber eventuell strings ausgegeben werden, für die in Stud.IP noch keine Übersetzung existieren,
+muss innerhalb des Plugins eine Übersetzungsdatei angelegt werden,
+die genau diese neuen string übersetzt.
+Bevor dies jedoch gemacht wird, sollten die entsprechenden strings als übersetzbar gekennzeichnet werden
+und damit dies nicht im Nachhinein für alle strings gemacht werden muss,
+wird dies eingeführt, bevor die eigentliche Funktionalität des Plugins erstellt wird.
+
+Innerhalb unseres Plugins muss mit `bindtextdomain` festgelegt werden,
+wo die Übersetzungsdatei zu finden ist und mit `bind_textdomain_codeset` die Zeichenkodierung festgelegt werden.
+Um dies nicht einzeln für alle Plugins innerhalb des Plugin-Verzeichnisses festzulegen,
+nutzen wir die vorher erstellten `Plugin`-Klasse, von der das `TextPlugin` erbt.
+
+```php
+<?php
+
+namespace TextPlugin;
+
+use StudIPPlugin;
+
+class Plugin extends StudIPPlugin
+{
+ const GETTEXT_DOMAIN = 'TextePlugin';
+
+ public function __construct()
+ {
+ parent::__construct();
+ bindtextdomain(static::GETTEXT_DOMAIN, $this->getPluginPath() . '/locale');
+ bind_textdomain_codeset(static::GETTEXT_DOMAIN, 'UTF-8');
+ }
+}
+```
+
+Damit innerhalb des Plugins nun lediglich `$this->_()` bzw. `$this->_n()` für gettext aufgerufen werden kann
+und nicht immer `dgettext()` bzw. `dngettext()`,
+sollten noch zwei Methoden in die Plugin-Klasse ergänzt werden:
+
+```php
+ public function _($string)
+ {
+ $result = dgettext(static::GETTEXT_DOMAIN, $string);
+
+ if ($result === $string) {
+ $result = _($string);
+ }
+
+ return $result;
+ }
+
+ public function _n($string0, $string1, $n)
+ {
+ if (is_array($n)) {
+ $n = count($n);
+ }
+
+ $result = dngettext(static::GETTEXT_DOMAIN, $string0, $string1, $n);
+
+ if ($result === $string0 || $result === $string1) {
+ $result = ngettext($string0, $string1, $n);
+ }
+
+ return $result;
+ }
+```
+
+Zum Erstellen der Navigation in `TextPlugin.class.php` hatten wir bereits zwei Texte erstellt ("Texte" und "Übersicht").
+Diese können wir nun mit dem Aufruf von `$this->_()` übersetzbar machen, sodass das TextPlugin folgendermaßen aussieht:
+
+```php
+<?php
+
+require_once __DIR__ . '/bootstrap.inc.php';
+
+class TextPlugin extends \TextPlugin\Plugin implements SystemPlugin
+{
+ public function __construct()
+ {
+ parent::__construct();
+
+ $root_nav = new Navigation($this->_('Texte'), PluginEngine::getURL($this, [], 'overview'));
+ $root_nav->setImage(Icon::create('file-text', Icon::ROLE_NAVIGATION));
+ Navigation::addItem('/text_root', $root_nav);
+
+ $navigation = new Navigation($this->_('Übersicht'), PluginEngine::getURL($this, [], 'overview'));
+ $root_nav->addSubNavigation('text_overview', $navigation);
+ }
+}
+
+```
+
+Nun möchten wir aber nicht nur die Texte übersetzen, die wir in einer Plugin-Klasse erstellen,
+sondern auch in Controller-Klassen und views.
+Dazu leiten wir alle Aufrufe von `_()` in Controllers auf das Plugin um,
+sodass wir in Controllers einfach komfortable `$this->_()` aufrufen können.
+Hierfür nutzen wir analog zur `Plugin`-Klasse die `Controller`-Klasse,
+damit wir dies direkt für alle Controller übernehmen.
+
+```php
+<?php
+
+namespace TextPlugin;
+
+use PluginController;
+use RuntimeException;
+
+class Controller extends PluginController
+{
+
+ public function __construct($dispatcher)
+ {
+ parent::__construct($dispatcher);
+
+ // Localization
+ $this->_ = function ($string) use ($dispatcher) {
+ return call_user_func_array(
+ [$dispatcher->current_plugin, '_'],
+ func_get_args()
+ );
+ };
+
+ $this->_n = function ($string0, $tring1, $n) use ($dispatcher) {
+ return call_user_func_array(
+ [$dispatcher->current_plugin, '_n'],
+ func_get_args()
+ );
+ };
+ }
+
+ public function __call($method, $arguments)
+ {
+ $variables = get_object_vars($this);
+ if (isset($variables[$method]) && is_callable($variables[$method])) {
+ return call_user_func_array($variables[$method], $arguments);
+ }
+ return parent::__call($method, $arguments);
+ }
+
+}
+```
+
+Nun sind Texte in Plugins und Controllers übersetzbar.
+Texte in javascript Dateien können wie im Kern mit `String.toLocaleString()` übersetzt werden.
+Texte in Model-Klassen wie `Text.php` müssen allerdings noch direkt
+mit dem Aufruf von `dgettext(Plugin::GETTEXT_DOMAIN, $string)` als übersetzbar gekennzeichnet werden.
+In view Dateien kann dahingehen `$controller->_($string)` oder alternativ `$_($string)` genutzt werden.
+
+#### Übersetzungsdatei anlegen
+Das Anlegen der Übersetzungsdatei geschieht in der Regel einfach mit dem Unix-Shellskript `makeStudIPPluginTranslations.sh`,
+welches auf der [Entwickler-Installation von Stud.IP](https://develop.studip.de/studip/dispatch.php/document/download/1bea6c139b56abc3ef0c505731bcc6b6) verfügbar ist.
+Es sammelt alle als übersetzbar gekennzeichneten strings und erstellt damit eine `.pot` Datei,
+weshalb die Übersetzungsdatei auch erst angelegt werden sollte,
+wenn das Plugin fertiggestellt wurde.
+Mithilfe eines Übersetzungseditors können die gesammelten Texte strings der `.pot`-Datei dann übersetzt werden
+und eine maschinenlesbare `.mo`-Datei erzeugt werden.
+
+## Zusammenfassung
+Es wurde
+
+* Eine `plugin.manifest` mit meta-daten erstellt ([Plugin-Manifest](PluginSchnittstelle#plugin-manifest))
+* Eine Plugin-Klasse zur Initialisierung erstellt ([Plugin-Interfaces](PluginSchnittstelle#plugin-interfaces))
+* Das Plugin installiert und aktiviert
+* Eine Navigation für eine Hauptseite erstellt ([Navigation](Navigation))
+* Jeweils eine Eltern-Klasse für Plugin- und Controller-Klassen erstellt
+* Mit dem Autoloader die Klassen in `classes` und später in `models` automatisch eingebunden
+* Ein Controller mit einer `action` und einer passender `view` erstellt ([Trails](Trails))
+* Eine Migration für die Datenbank-Tabellen-Struktur ([Migrationen](Migrations))
+* Eine SORM-Klasse für die Datenbank-Tabellen erstellt ([SimpleORMap](SimpleORMap))
+* Erläutert wie js- und cc-Dateien einzubinden sind
+* Eine Basis erstellt, um strings zu übersetzen ([Internationalisierung](Howto/Internationalisierung))
+
+Der komplette bisher erstellte Code ist hier ([TextPlugin.zip](../assets/862928c482008450a61d0c4156987dee/TextPlugin.zip)) als ZIP-Datei verfügbar.
+
+Zum [zweiten Teil](Plugin-Tutorial-II-(Beispiel-Funktionalitäten))
diff --git a/docs/docs/plugins/tutorial.md b/docs/docs/plugins/tutorial.md
new file mode 100644
index 0000000..5fb05f8
--- /dev/null
+++ b/docs/docs/plugins/tutorial.md
@@ -0,0 +1,512 @@
+---
+title: Plugin-Tutorial
+sidebar_label: Tutorial
+---
+
+
+In diesem Tutorial wird die Erstellung von Plugins vorgestellt. Da es in Stud.IP verschiedene Arten von Plugins gibt, werden diese anhand verschiedener Beispiele verdeutlicht.
+
+### Das Grundgerüst bauen
+
+Ein Stud.IP-Plugin besteht immer aus mindestens zwei Dateien: Der Plugin-Klasse mit dem PHP-Code und einer Textdatei mit Informationen über das Plugin (Name, Version, Autor usw.). Im Folgenden wird das Grundgerüst eines Plugins erstellt, welches nichts weiter macht als sich im System zu registrieren.
+
+#### Plugin-Klasse
+
+Die Plugin-Klasse kann im einfachsten Fall so aussehen:
+
+**MyPlugin.php**
+
+```php
+<?php
+
+class MyPlugin extends StudipPlugin implements SystemPlugin
+{
+}
+```
+
+Wichtig ist hierbei:
+* Die Datei ist nach der Klasse ("`MyPlugin`") benannt und hat die Endung "`.class.php`".
+* Die eigene Plugin-Klasse muss von der Klasse `StudIPPlugin` abgeleitet werden.
+* Die Plugin-Klasse muss mindestens eines der Interfaces implementieren, die festlegen, an welchen Stellen in Stud.IP es aktiv werden kann. Im obigen Beispiel ist es das Interface `SystemPlugin`, womit das Plugin auf jeder Seite aktiv sein soll (mehr dazu später).
+
+Das Plugin selbst tut nichts und tritt damit außerhalb der Plugin-Verwaltung nicht in Erscheinung.
+
+#### plugin.manifest
+
+Die Datei mit den Informationen über das Plugin wird von Stud.IP (unter anderem) verwendet, um dem Administrator Informationen zum Plugin anzeigen zu können. Sie hat immer den Namen "[`plugin.manifest`](PluginSchnittstelle#toc4)" und sollte mindestens folgende Einträge enthalten:
+
+* *pluginname*: Der Name für das Plugin
+* *pluginclassname*: Der Name der Plugin-Klasse (in PHP). Falls die Endung .class.php verwendet wird, kann das .class hier weggelassen werden.
+* *origin*: Der Name oder die Mail-Adresse des Autors oder dessen Institution oder Gruppe
+* *version*: Die Versionsnummer des Plugins
+* *description*: Eine kurze Beschreibung, was das Plugin macht
+* *studipMinVersion*: Die minimal erforderliche Stud.IP Version
+
+Eine plugin.manifest Datei kann beispielsweise folgenden Inhalt haben:
+
+```ini
+pluginname=MyPlugin
+pluginclassname=MyPlugin
+origin=elmar@example.com
+version=1.0
+description=Ein völlig nutzloses Beispiel-Plugin
+studipMinVersion=2.0
+```
+
+
+#### Plugin installieren
+
+Aus den Plugin-Dateien muss nun ein Installationsarchiv erstellt werden. Dazu packt man alle Plugin-Dateien in ein ZIP-Archiv ein. Hierbei ist es wichtig, den Ordner, in dem das Plugin liegt, nicht mit einzupacken, da sonst die Installation fehlschlägt. Im Wurzelverzeichnis des Archivs sollten also direkt die Dateien plugin.manifest und die PHP-Datei mit der Plugin-Klasse sichtbar sein:
+
+```text
+Length Date Time Name
+--------- ---------- ----- ----
+ 71 2011-04-20 11:26 MyPlugin.php
+ 152 2011-04-20 11:26 plugin.manifest
+--------- -------
+ 223 2 files
+```
+
+Anschließend kann man das Plugin über die [Plugin-Verwaltung](http://docs.studip.de/admin/Admins/PluginVerwaltung) in Stud.IP installieren. Am einfachsten geht dies, indem man das ZIP-Archiv einfach in den Drag&Drop-Bereich auf der linken Seite der Plugin-Verwaltung zieht.
+
+Attach:plugin-install.png
+
+Nach der Installation muss das Plugin auf der Seite der Plugin-Verwaltung noch aktiviert werden, damit es auch tatsächlich geladen wird. Außerdem kann man in der Spalte "Aktionen" die Rechteverwaltung für das Plugin vornehmen. In der Voreinstellung kann ein Plugin von allen angemeldeten Nutzern im System verwendet werden.
+
+#### Das Plugin ausbauen
+
+Nach der Installation des Plugins liegt dieses im Ordner `/public/plugins_packages/<origin>/<Plugin-Name>/`. "origin" ist hierbei der Wert des Konfigurationsparameters "origin" aus der plugin.manifest Datei. Während der Entwicklung des Plugins kann es einfacher sein, wenn man die installierte Version des Plugins unterhalb des Stud.IP Verzeichnisses weiter bearbeitet, da man sich dann die Neuinstallation des Plugins nach jeder Änderung sparen kann.
+
+Je nachdem, welche Art von Plugin man programmieren möchte, muss das Plugin von unterschiedlichen Klassen abgeleitet werden und es müssen unterschiedliche Methoden implementiert werden.
+
+### Vorstellung verschiedener Plugin-Typen
+
+#### SystemPlugin
+
+Angenommen, wir wollen, dass hinter dem Namen der Stud.IP-Installation in der Kopfzeile immer die Anmeldekennung des gerade angemeldeten Nutzers angezeigt wird. Wie funktioniert so etwas?
+
+* Wir müssen dafür sorgen, dass das Plugin auf jeder Seite aktiv ist.
+* Wir müssen irgendwie die Kennung des angemeldeten Nutzers ermitteln.
+* Wir müssen diesen Text in die Kopfzeile einbauen können.
+
+Der Punkt 1 ist zum Glück bereits erledigt: Da unser Plugin das Interface `SystemPlugin` implementiert, ist es automatisch auf jeder Seite aktiv.
+
+Punkt 2 führt uns direkt in die Untiefen der Stud.IP-API. Zum Glück gibt es hier aber eine sehr einfach zu bedienende Funktion, die uns die aktuelle Nutzerkennung liefert: `get_username()`.
+
+Für Punkt 3 kann man sich etwas CSS bedienen, um Inhalte auf einer Seite zu verändern:
+
+```css
+#id:after {
+ content: "some text";
+}
+```
+
+Dieses Stück CSS sorgt dafür, daß hinter dem Element mit der ID "`id`" im HTML der Text "some text" angezeigt wird. In unserem Fall hat das gesuchte Element in der Kopfzeile die ID "barTopFont". Soweit, so gut. Aber wie bekommen wir das CSS auf die Stud.IP-Seite?
+
+Dazu gibt es eine API in Stud.IP, die es Plugins ermöglicht, Eingriffe in den Seitenaufbau vorzunehmen: [PageLayout](PageLayout). Unter anderem kann man damit auch eigenes CSS in die erzeugte Seite einbauen. Zusammen sieht das ganze dann etwa so aus:
+
+```php
+class MyPlugin extends StudipPlugin implements SystemPlugin
+{
+ public function __construct()
+ {
+ parent::__construct();
+
+ $username = get_username();
+ $css = "#barTopFont:after { content: '[$username]'; }";
+ PageLayout::addStyle($css);
+ }
+}
+```
+
+Da ein System-Plugin keinen speziellen Einsprungpunkt für Stud.IP hat, wird der Code direkt in den Konstruktor integriert. Natürlich sollte man hier nicht vergessen, den Konstruktor der Basisklasse aufzurufen... Das fertige Plugin sieht dann so aus:
+
+Attach:myplugin.png
+
+##### Quellcode des Plugins
+
+Attach:MyPlugin.zip
+
+#### PortalPlugin – Ein eigenes Plugin auf der Startseite
+
+Kommen wir nun zu einem weiteren Beispiel, das zeigt, wie ein Plugin eigene Inhalte auf der Startseite anzeigen kann. Als Beispiel wollen wir bei jedem Aufruf der Seite ein zufälliges Zitat aus einer bekannten Fernsehserie einblenden. Und das sowohl für angemeldete als auch für nicht angemeldetet Nutzer.
+
+##### Das Interface `PortalPlugin`
+
+Das Plugin-Interface PortalPlugin bietet jedem Plugin die Möglichkeit, einen eigenen Kasten auf der Startseite zu platzieren, ganz analog zu den vorhandenen Kästen für die Termine, systemweiten Ankündigungen oder Umfragen. Dieses sieht folgendermaßen aus:
+
+```php
+interface PortalPlugin
+{
+ /**
+ * Return a template (an instance of the Flexi_Template class)
+ * to be rendered on the start or portal page. Return NULL to
+ * render nothing for this plugin.
+ *
+ * The template will automatically get a standard layout, which
+ * can be configured via attributes set on the template:
+ *
+ * title title to display, defaults to plugin name
+ * icon_url icon for this plugin (if any)
+ * admin_url admin link for this plugin (if any)
+ * admin_title title for admin link (default: Administration)
+ *
+ * @return object template object to render or NULL
+ */
+ function getPortalTemplate();
+}
+```
+
+Offensichtlich benötigt man ein *Template*-Objekt für die Ausgabe, und man kann noch gewisse Eigenschaften des erzeugten Kastens vorgeben (Icon, Titel usw.).
+
+##### Wichtig: Flexi-Templates
+
+Text- oder HTML-Ausgabe sollte in Stud.IP immer über *Templates* passieren, das sind (in PHP geschriebene) Vorlagen für die Ausgabe, die mit Platzhaltern bestückt sein können, um Werte aus dem Programmcode an bestimmte Stellen in der Ausgabe zu bringen. Eine komplette Beschreibung mit vielen Beispielen befindet sich hier im Wiki unter dem Punkt [FlexiTemplates](FlexiTemplates).
+
+In unserem Fall brauchen wir nur einen kleinen Bereich, in dem ein vorformatierter Text angezeigt werden kann. Das Template dafür steht in einer eigenen PHP-Datei und kann etwa so aussehen:
+
+**templates/fortune.php**
+
+```php
+<pre>
+<?= htmlReady($fortune) ?>
+</pre>
+```
+
+Das "`<pre>`" sollte nicht überraschend sein, aber wieso steht da *htmlReady()*? Nun, damit spezielle Zeichen in dem auszugebenden Text nicht versehentlich vom Browser als HTML-Markup ausgewertet werden (man stelle sich vor, die Variable $fortune enthielte selbst Dinge wie "`<b>`"), müssen diese vor der Ausgabe entsprechend kodiert werden. Aus "`<b>`" würde dann "&lt;b&gt;", was vom Browser wieder als "`<b>`" angezeigt würde.
+
+Daher ist es wichtig, bei jeder Ausgabe eines Werts in einem Template an die Verwendung von *htmlReady()* (oder *formatReady()*, wenn man mit der Stud.IP-Formatierung arbeitet) zu denken. Die einzige Ausnahme sind Werte, die bereits fertige HTML-Fragmente für die Anzeige enthalten. Ein Beispiel dafür sehen wir später.
+
+##### Die Plugin-Klasse
+
+Das Plugin wird wie im ersten Beispiel von der Klasse `StudipPlugin` abgeleitet, implementiert jetzt aber - wie oben besprochen - das Interface `PortalPlugin`. Zusätzlich benötigen wir nun noch eine eigene *TemplateFactoy* für unser Plugin, damit das Template aus dem Ordner des Plugins geladen werden kann. Es ist üblich, alle Templates für ein Plugin in einem Ordner mit dem Namen "`templatess`" abzulegen. Der Code zum Laden des Templates sieht dann so aus:
+
+```php
+$template_path = $this->getPluginPath().'/templates';
+$template_factory = new Flexi_TemplateFactory($template_path);
+$template = $template_factory->open('fortune');
+```
+
+Die Methode *getPluginPath()* eines Plugins liefert einen Dateisystempfad zum Installationsordner des Plugins, relativ zu diesem Ort kann man dann z.B. Template-Dateien laden. Dieser Pfad ist aber nur serverseitig gültig, er kann nicht zum erzeugen von URLs verwendet werden.
+
+Schließlich soll unser Plugin noch ein Icon und einen Titel für die Anzeige bekommen. Dazu muß man die entsprechenden Attribute "icon_url" und "title" in dem Template setzen. Als Titel setzen wir einfach den Namen des Plugins ein. Für die URL des Icons benötigen wir eine URL zu einer Ressource im Plugin, dazu gibt es - analog zu *getPluginPath()* für serverseitge Pfade - auch eine Methode *getPluginURL()*, die eine für den Nutzer gültige URL auf den Installationsort des Plugins liefert. Diese URL kann dann als Basis für die Icon-URL verwendet werden:
+
+```php
+$template->title = $this->getPluginName();
+$template->icon_url = $this->getPluginURL() . '/images/icon.gif';
+```
+
+Schließlich muß noch die Template-Varianble *$fortune* aus der Template-Datei mit einem Zitattext gefüllt werden. Eine einfache Möglichkeit ist, einfach das (hoffentlich auf dem Rechner installierte) *fortune* Kommando aufzurufen, das gleiche eine entsprechende Zitat-Datenbank mitbringt. Die komplette Plugin-Klasse sieht dann am Ende so aus:
+
+```php
+class Fortune extends StudipPlugin implements PortalPlugin
+{
+ public function getPortalTemplate()
+ {
+ $template_path = $this->getPluginPath().'/templates';
+ $template_factory = new Flexi_TemplateFactory($template_path);
+ $template = $template_factory->open('fortune');
+
+ $template->title = $this->getPluginName();
+ $template->icon_url = $this->getPluginURL() . '/images/icon.gif';
+
+ $template->fortune = shell_exec('/usr/games/fortune startrek');
+ return $template;
+ }
+}
+```
+
+Im template-Verzeichnis des Plugins muss nun folgende Template Datei namens fortune.php angelegt werden:
+
+```php
+<div id="fortune">
+ <?= $fortune ?>
+</div>
+```
+
+#### HomepagePlugin - Ein Plugin auf der Profilseite
+
+Im nächsten Beispiel geht es nun um ein Plugin auf der Profilseite. Als Aufgabe nehmen wir uns vor, eine einfache Version der "Eigenen Kategorien" des Profils in einem Plugin nachzubauen: Es soll in einem Kasten im Nutzerprofil ein (ggf. formatierter) Text angezeigt werden, der dort vom Nutzer selbst auch bearbeitet werden kann. Im Gegensatz zu den "Eigenen Kategorien" gibt es aber immer nur einen Kasten und es können weder Titel noch die Sichtbarkeit eingestellt werden. Dazu müssen wir uns mit folgenden neuen Fragen beschäftigen:
+
+# Anzeige von Inhalten auf der Profilseite
+# Umgang mit der Stud.IP-Formatierung
+# Ausgabe und Auswertung von Formularen (Nutzerinteraktion)
+# Speichern von Nutzereingaben in der Datenbank
+
+##### Das Interface `HomepagePlugin`
+
+Ganz analog zur Anzeige von eigenen Inhalten auf der Startseite gibt es auch ein entsprechendes Interface zur Anzeige von Inhalten auf der Profilseite: `HomepagePlugin`. Es bietet die gleichen Möglichkeiten und wird auch exakt genauso benutzt:
+
+```php
+interface HomepagePlugin
+{
+ /**
+ * Return a template (an instance of the Flexi_Template class)
+ * to be rendered on the given user's home page. Return NULL to
+ * render nothing for this plugin.
+ *
+ * The template will automatically get a standard layout, which
+ * can be configured via attributes set on the template:
+ *
+ * title title to display, defaults to plugin name
+ * icon_url icon for this plugin (if any)
+ * admin_url admin link for this plugin (if any)
+ * admin_title title for admin link (default: Administration)
+ *
+ * @return object template object to render or NULL
+ */
+ function getHomepageTemplate($user_id);
+}
+```
+
+Auch hier benötigt man natürlich wieder ein Template-Objekt und entsprechende Template-Dateien für die Ausgabe.
+
+#### Ausgabe von formatiertem Text
+
+Das Template für die Anzeige kann wieder sehr einfach gehalten werden:
+
+Attach:editbox.png
+
+**templates/fortune.php**
+
+```php
+<div id="edit_box">
+ <?= formatReady($text) ?>
+</div>
+```
+
+Anders als bei dem vorhergehenden Beispiel wird hier die Funktion *formatReady()* aufgerufen, um den auszugebenden Text für die Anzeige aufzubereiten. Während *htmlReady()* sich nur um die Kodierung von Sonderzeichen kümmert, wertet *formatReady()* zusätzlich auch die Stud.IP-Formatierungssyntax aus, d.h. bestimmte [Markierungen im Text](http://docs.studip.de/help/2.0/de/Basis/VerschiedenesFormat) führen zu speziellen Hervorhebungen bei der Anzeige. Zusätzlich können auch Listen, Tabellen, Links, Bilder und anderes angezeigt werden. Die "*id*" auf dem umschließenden DIV wird später verwendet, um diesen Punkt auf der Profilseite gezielt anspringen zu können.
+
+Für die Erstellung des Templates kann man im wesentlichen den Code aus dem letzten Beispiel wiederverwenden (auf ein Icon verzichten wir hier). Der anzuzeigende Inhalt kommt von einer eigenen Methode *getContents()*, die später den Text aus der Datenbank lesen wird:
+
+```php
+class EditBox extends StudipPlugin implements HomepagePlugin
+{
+ public function getHomepageTemplate($user_id)
+ {
+ $template_path = $this->getPluginPath().'/templates';
+ $template_factory = new Flexi_TemplateFactory($template_path);
+ $template = $template_factory->open('edit_box');
+
+ $template->title = $this->getPluginName();
+ $template->text = $this->getContents($user_id);
+ return $template;
+ }
+}
+```
+
+##### Formulare und URLs
+
+Das oben gezeigte Template erlaubt noch keine Bearbeitung des angezeigen Inhaltes. Darum soll es in nun diesem Abschnitt gehen: Wir brauchen dazu noch ein entsprechendes Template für die Eingabe - also ein HTML-Formular - sowie etwas Logik in unserem Plugin zur Auswertung dieser Eingabe. Falls der Stud.IP-Nutzer seine eigene Profilseite aufruft, sollte er einen speziellen Editiermodus des Plugins aktivieren können (dazu gleich mehr), der dann ein entsprechedes Formular anzeigt:
+
+Attach:editbox-edit.png
+
+**templates/edit_mode.php**
+
+```php
+<form id="edit_box" action="<?= URLHelper::getLink('#edit_box') ?>" method="POST">
+ <textarea name="text" style="display: block; width: 80%; height: 8em;"><?=
+ htmlReady($text)
+ ?></textarea>
+ <?= makeButton('uebernehmen', 'input', false, 'save') ?>
+ <?= makeButton('abbrechen', 'input', false, 'cancel') ?>
+</form>
+```
+
+Das Formular ist sehr einfach aufgebaut: Es gibt eine TEXTAREA zum Bearbeiten des Inhalts sowie zwei Schaltflächen zum Speichern bzw. Verwerfen der Änderungen. Auch hier darf das *htmlReady()* natürlich nicht fehlen. Ein *formatReady()* wäre an dieser Stelle übrigens falsch, da wir ja nicht die bereits formatierte Ansicht bearbeiten wollen. Zur Anzeige von Formularschaltflächen gibt es eine Hilfsfunktion *[makeButton()](http://hilfe.studip.de/api/language_8inc_8php.html#a029ae0013a8aa35f8cea9e5ab43cda16)* in Stud.IP, die wir auch hier verwenden. Dies ist ein Beispiel für eine Funktion, die bereits fertige HTML-Fragmente liefert, das Resultat von *makeButton()* darf also nicht mehr mit htmlReady() behandelt werden. Das von makeButton() erzeugte HTML wird hinterher etwa so aussehen:
+
+```php
+<input class="button" type="image" src="[...]/uebernehmen-button.png" name="save">
+<input class="button" type="image" src="[...]/abbrechen-button.png" name="cancel">
+```
+
+Beim Absenden des Formulars soll unser Plugin die eingegebenen Daten verarbeiten können, wir müssen also dafür sorgen, daß wieder die Profilseite (dort lebt ja das Plugin) angezeigt wird, zusätzlich soll der Kasten des Plugins direkt angesprungen werden. Als URL für das Formular müssen wir also eine passende URL zur Profilseite generieren, inklusive Ansprungpunkt auf der Seite. URLs auf Seiten in Stud.IP werden immer - bis auf wenige, spezielle Ausnahmen - über die Klasse [URLHelper](URLHelper) erzeugt. Im einfachsten Fall gibt man nur den Namen des ensprechenden PHP-Skriptes für die Seite im Aufruf von *URLHelper::getURL()* an und bekommt die entsprechende URL zurück. Hier ist es sogar noch einfacher: Wir befinden uns ja schon auf der Profilseite, müssen also nur den Ansprungpunkt auf der aktuellen Seite angeben: "`#edit_box`".
+
+Auch hier gilt: Beim Einsetzen von Werten in HTML muß man sich immer überlegen, ob noch ein *htmlReady()* erforderlich ist. Normalerweise wäre das der Fall, da das Erzeugen von Links aber recht häufig vorkommt, gibt es hierzu eine Hilfsfunktion im `URLHelper`, die das Kodieren gleich mit erledigt: *URLHelper::getLink()*. Das Resultat dieser Funktion kann also (wie *makeButton()*) immer direkt in die Ausgabe eingesetzt werden.
+
+##### Verarbeitung von Nutzereingaben
+
+Unser Plugin kennt zwei Arten von Nutzerinteraktion: Aktivieren des *Editiermodus* und Abspeichern bzw. Verwerfen der Formulareingaben. Beides ist nur für den Besitzer des angezeigten Profils erlaubt. Um darauf reagieren zu können, müssen wir unsere Plugin-Methode um einige Code-Zeilen erweitern (und zwar in der Funktion getHomepageTemplate zwischen den Zeilen $template = $template_factory->open('edit_box'); und $template->title = $this->getPluginName();) :
+
+```php
+if ($user_id == $GLOBALS['user']->id) {
+ if (Request::int('edit')) {
+ $template = $template_factory->open('edit_mode');
+ } else if (Request::submitted('save')) {
+ $this->setContents($user_id, Request::get('text'));
+ }
+
+ $template->admin_url = URLHelper::getURL('#edit_box', array('edit' => 1));
+ $template->admin_title = 'Inhalt bearbeiten';
+}
+```
+
+In der globalen Variablen `$user` ist der gerade in Stud.IP angemeldete Nutzer hinterlegt. Wir können also leicht überprüfen, ob der aktuelle Nutzer auch der Besitzer des angezeigten Profils ist. Falls ja, kann der Editiermodus über ein spezielles Icon in der Titelzeile des Kastens für das Plugin angewählt werden (ganz rechts, analog zu dem Icon für eigene Ankündigungen und Umfragen). Über das Template kann der Link, ggf. mit weiteren URL-Parametern, und ein Tooltip für das Icon vorgegeben werden. Ist der Editiermodus aktiviert worden, wird das entsprechende Template geladen.
+
+Zur Abfrage von URL- und Formular-Parametern in Stud.IP sollte man immer die Klasse [Request](Request) verwenden, die unter anderem auch typsicheren Zugriff auf die Parameterwerte erlaubt. Die Namen der Parameter entsprechen denen aus dem Template, d.h. "`Request::submitted('save')`" ermittelt, ob die Schaltfläche mit dem Namen "save" im Formular angeklickt wurde.
+
+
+#### Plugins mit Navigation
+
+Die bisher gezeigten Beispiele für Plugins haben sich alle in vorhandene Seiten integriert, ein Plugin kann aber auch komplett eigene Seiten in Stud.IP anbieten oder sogar Inhalte ausliefern, die gar nicht auf das Stud.IP-Design zurückgreifen wie Web-Services oder Datei-Downloads. Wie das funktioniert wird im diesem Abschitt beschrieben. Auch hier fangen wie wieder mit einem kleinen Beispiel an, diesmal soll die Aufgabe so aussehen:
+
+* Das Plugin soll ein eigenes Icon in der Navigation bekommen.
+* Das Plugin soll als Punkt auf der Startseite verlinkt sein.
+* Es soll beim Aufruf eine komplett eigenständige Seite anzeigen, inklusive einer Infobox:
+
+Attach:demoplugin.png
+
+
+### Weitere Entwicklungsschritte
+
+#### Zugriff auf die Datenbank
+
+Zugriffe auf die Datenbank werden in Stud.IP über [SimpleORMap](SimpleORMap) (SORM) durchgeführt. Plugins können eine eigene SimpleORMap-Klasse definieren und damit die Vorteile von SimpleORMap nutzen.
+
+##### Neue Tabelle mittels Migration erstellen
+
+Auch wenn es sich um das Anlegen einer neuen Datenbanktabelle handelt, kann hierfür eine Migration verwendet werden. Dadurch werden das Anlegen der Datenbanktabelle und spätere Migrationen auf die gleiche Art und Weise durchgeführt.
+
+Zum Erstellen einer Migration wird im Plugin-Verzeichnis ein neuer Ordner namens "migrations" erstellt. In diesem werden nummerierte Migrations-Dateien untergebracht, wobei es sich um PHP-Skripte handelt, welche eine einzige Klasse enthalten und deren Dateiname einem besonderen Schema entspricht. Der Dateiname darf nur kleingeschriebene Buchstaben beinhalten, da sonst keine Migration durchgeführt werden kann. 01_initial.php wäre zum Beispiel ein gültiger Dateiname, während 01_Initial.php (großes "i") zu einem Fehler führen würde.
+
+In der Migrations-Datei wird nun eine Klasse erzeugt, welche die Klasse "Migration" erweitert. Ihr Name kann aus Gründen der Einfachheit genauso gewählt werden wie der Dateiname hinter der Nummerierung, im Beispiel also "Initial" (hier sind Großbuchstaben erlaubt). Die Klasse muss die Methoden up() und down() implementieren. up() dient zum Durchführen einer Migration, während down() die Änderungen an Datenbanktabellen, welche mit up() durchgeführt wurden, rückgängig macht. Zum Zugriff auf die Datenbank bedient man sich der Klasse DBManager, welche durch die Methode get() eine Datenbankverbindung zurückliefert:
+
+`$db = DBManager::get();`
+
+Nun kann mit `$db->exec` SQL-Code auf der Datenbank ausgeführt werden, wie das folgende Beispiel zeigt:
+
+```php
+$db->exec("CREATE TABLE edit_box (
+ user_id varchar(32) NOT NULL,
+ content text NOT NULL,
+ PRIMARY KEY (user_id)
+);"
+ );
+```
+
+Die Tabelle wurde angelegt, ist aber noch leer. An dieser Stelle kann natürlich noch mit einem zweiten `$db->exec` Aufruf und einer `INSERT INTO` SQL-Anweisung die Tabelle gefüllt werden. Damit ist die up()-Methode fertig. Nun muss die down()-Methode geschrieben werden. Da es sich um die erste Migration handelt, kann beim rückgängig machen der Migration die Tabelle gelöscht werden:
+
+```php
+$db = DBManager::get();
+$db->exec("DROP TABLE hallo_welt;");
+```
+
+Damit ist die Migrations-Datei fertig bearbeitet.
+
+
+##### Anlegen einer SimpleORMap-Klasse
+
+Nun muss im Plugin eine SORM-Klasse angelegt werden, mit der Einträge aus der Datenbank-Tabelle in Objekte umgewandelt werden können. Dazu legt man im Plugin-Verzeichnis den Unterordner models an und in diesem eine PHP-Datei, welche die Datenklasse, welche man gerne in der Datenbank haben möchte, beinhaltet. Der Name der PHP-Datei muss dem Namen der Klasse entsprechen. Die Klassendefinition kann im einfachen Fall folgendermaßen aussehen:
+
+```php
+class EditBox extends SimpleORMap {
+
+ static protected function configure($config = array()) {
+
+ $config['db_table'] = 'edit_box';
+
+ parent::configure($config);
+ }
+}
+```
+
+Damit können Objekte der Klasse EditBox aus der Datenbank geholt werden, sofern die Datenbanktabelle existiert.
+
+
+**Hinweis:** Man sollte beim Erstellen von neuen Tabellen möglichst immer die Standardeinstellungen der Datenbank übernehmen, d.h. keine Zeichenkodierung oder Storage-Engine vorgeben.
+
+##### Von der Datenbank lesen und schreiben
+
+Lese- und Schreibzugriffe werden im Wiki-Artikel [SimpleORMap](SimpleORMap) erklärt.
+
+
+#### Lokalisierung mit gettext
+
+Zur Lokalisierung eines Plugins sind mehrere Schritte notwendig. Zuerst müssen die Templates zur Übersetzung vorbereitet werden. Mit deren Hilfe werden die Übersetzungsdateien erzeugt.
+
+##### Übersetzungen in Templates
+
+Um in den Templates eines Plugins Übersetzungen verwenden zu können, wird die Funktion dgettext verwendet. Diese funktioniert fast wie gettext, mit dem Unterschied, dass dgettext zuerst die Übersetzungs-Domäne mitgegeben wird, bevor der zu übersetzende String übergeben wird. Dies hängt damit zusammen, dass die zu übersetzenden Strings des Plugins nicht in den Übersetzungsdateien von Stud.IP gefunden werden können und deswegen im Plugin gesonderte Übersetzungsdateien angelegt werden müssen.
+Ein zu übersetzender Text wird folgendermaßen umgeschrieben:
+vorher: `echo "Hallo Welt!";`
+nachher: `echo dgettext("MyPlugin", "Hallo Welt!");`
+
+Mittels dgettext wurde angegeben, dass die Übersetzung der Zeichenkette "Hallo Welt" in der Übersetzungs-Domäne "MyPlugin" gefunden werden kann, welche nur im Plugin genutzt wird.
+
+##### Anlegen der Übersetzungsdateien
+
+Zum Anlegen der Übersetzungsdateien kann das Unix-Shellskript makeStudIPPluginTranslations.sh verwendet werden, welches für die Übersetzung in mehrere Sprachen verwendet werden kann und einfach für andere Projekte angepasst werden kann. Es befindet sich auf der Entwickler-Installation von Stud.IP: [https://develop.studip.de/studip/dispatch.php/document/download/1bea6c139b56abc3ef0c505731bcc6b6](https://develop.studip.de/studip/dispatch.php/document/download/1bea6c139b56abc3ef0c505731bcc6b6)
+
+Die Ordnerstruktur, in welcher die Übersetzungsdateien unterhalb des Plugin-Verzeichnisses liegen, muss exakt dem folgenden Schema entsprechen: /locale/<Abkürzung der Sprache>/LC_MESSAGES/. Neben Englisch sind natürlich auch weitere Sprachen möglich. Da zurzeit (Juni 2016) in Stud.IP nur Deutsch und Englisch als Sprachen verfügbar sind, können zusätzlichen Sprachen, in die das Plugin übersetzt wurde, nicht aktiviert werden.
+Nach der Ausführung des Skriptes liegen im Ordner LC_MESSAGES eine Datei vor: MyPlugin.pot. Diese kann nun mit einem Übersetzungs-Editor, wie beispielsweise Poedit, bearbeitet werden. Der Editor sollte beim Speichern der Übersetzungen aus der pot-Datei automatisch eine .mo-Datei erzeugen, sodass die Übersetzung in maschinenlesbarer Form vorliegt.
+
+#### Anpassungen im Konstruktor der Plugin-Klasse
+
+Um festzulegen, dass Übersetzungen im Plugin von der eigenen Übersetzungs-Domäne bezogen werden sollen, muss im Konstruktor der Plugin-Klasse folgender Code eingefügt werden:
+`bindtextdomain('MyPlugin', __DIR__ . '/locale');`
+
+Mittels bindtextdomain wird die Übersetzungs-Domäne auf "MyPlugin" festgelegt. Damit gettext auch weis, wo die zugehörigen Übersetzungsdateien zu finden sind, wird der absolute Pfad zum Unterordner des Plugins, in welchem die Übersetzungen liegen, benötigt.
+WICHTIG: `$this->getPluginPath()` liefert nur einen relativen Pfad, welcher in einem Unterordner des Stud.IP Installationsverzeichnisses beginnt und kann deswegen an dieser Stelle nicht verwendet werden, um den Pfad zu den Übersetzungsdateien anzugeben!
+
+Nach diesen Schritten sind alle Voraussetzungen erfüllt, um ein Plugin lokalisieren zu können.
+
+#### Controller im Plugin anlegen
+
+Es kann notwendig sein, dass ein Plugin eigene Controller besitzt, welche eigene Seiten im Plugin bereitstellen. Solche Seiten werden in [Trails](Trails) realisiert. Trails ist ein Framework, welches das MVC-Paradigma umsetzt, sodass die Programmlogik (Controller) von der HTML-Ausgabe (View) und dem Datenbankmodell (Models) getrennt ist. Da in Stud.IP bereits SimpleORMap für die Umsetzung von Modellen verwendet wird, bleiben nur noch Ansichten (views) und Controller übrig, die über Trails umgesetzt werden müssen.
+
+##### Erstellen eines Controllers
+
+Im Verzeichnis des Plugins wird ein Unterordner namens "controllers" angelegt. In diesem wird für jeden Controller eine eigene PHP-Datei angelegt, in welcher jeweils nur eine Controller-Klasse enthalten ist. Der Dateiname, in welchem der Controller enthalten ist, wird in Kleinbuchstaben gehalten. Die Klasse, welche den Controller implementiert, wird in der üblichen Notation (Großbuchstaben an jedem Wortanfang, ohne Unterstriche) geschrieben. Sie erweitert die Klasse PluginController.
+
+Vor der Klassendefinition sollte aus Kompatibilitätsgründen mit alten Stud.IP Versionen noch folgende Zeile eingefügt werden:
+`require_once('app/controllers/plugin_controller.php');`
+
+Ein einfacher Controller kann so aussehen:
+
+```php
+<?php
+class HalloController extends PluginController {
+ public function index_action() {
+ $this->text = dgettext('MyPlugin', 'Hallo Welt!');
+ }
+}
+```
+
+##### Erstellen einer Ansicht (view)
+
+Jede Ansicht muss im Unterordner "views" des Plugin-Ordners angelegt werden. Dort wird für jeden Controller ein eigener Unterordner erzeugt, in welchem dann die einzelnen Ansichten abgelegt sind. Für jede Aktion eines Controllers wird eine eigene Ansicht erzeugt, wobei jede Ansicht ihre eigene PHP-Datei besitzt. In einer Ansicht kann beliebiger HTML-Code untergebracht sein. Attribute des Controllers sind als einfache Variablen in normaler PHP-Syntax abrufbar.
+
+Der obige Controller besitzt nur eine Aktion: "index". Außerdem heißt er "HalloController" und seine PHP-Datei heißt folglich hallo.php und liegt im Ordner /controllers/. Die zugehörige Ansicht (view) muss im Ordner /views/hallo/index liegen. Für obigen Controller kann sich die Ansicht auf folgenden Code beschränken:
+
+```php
+<strong><?= $text; ?></strong>
+```
+
+Das Attribut `$this->text` aus dem Controller wurde einfach zu `$text`. Andere Attribute der Controller-Klasse werden ebenfalls der Ansicht übergeben, beispielsweise das Attribute `$this->plugin`, welches vordefiniert ist.
+
+#### Zugriff für nicht angemeldete Nutzer
+
+Wollen wir, daß unser Plugin auch für nicht angemeldete Nutzer sichtbar ist, so muß man der Installation noch bei den Rechteeinstellungen auswählen, daß neben den voreingestellten Standardrollen auch die Rolle "nobody" (diese Rolle ist speziell für nicht angemeldete Nutzer) das Plugin verwenden kann:
+
+Attach:roles-nobody.png
+
+Das installierte Plugin sieht dann beim Aufruf im System so aus:
+
+Attach:fortune.png
+
+##### Quellcode des Plugins
+
+Attach:Fortune.zip
+
+### Veröffentlichung
+
+Ist das eigene Plugin funktionsfähig, kann es auf den Stud.IP Marktplatz hochgeladen werden, damit andere das Plugin testen und nutzen können.
+
+Um ein Plugin in den Martkplatz einstellen zu können, ist ein Benutzerkonto auf der Stud.IP Installation [https://develop.studip.de](https://develop.studip.de) erforderlich. Dort gibt es in der oberen Leiste einen Reiter namens PluginMarktplatz. Auf diesem befindet sich ein Reiter namens "Meine Plugins", unter welchem das HalloWelt-Plugin hochgeladen werden kann. Nach dem Klick auf "Meine Plugins" wählt man dazu im linken Bereich "Neues Plugin eintragen" aus und füllt den sich öffnenden Dialog auf. Bildschirmfotos vom Plugin zeigen anderen Benutzern schnell, was das Plugin alles kann und sollten daher hinzugefügt werden.
+
+Im Punkt "Release hinzufügen" wählt man "als Datei" aus. Nun packt man das fertige Plugin erneut in eine ZIP-Datei und wählt diese nach dem Klick auf den "Durchsuchen" Button im Dialog aus. Nach dem Klick auf "Speichern" wurde das Plugin hochgeladen und muss von einem Administrator des Marktplatzes freigeschaltet werden. Sobald dies geschehen ist, wird das Plugin auf der Startseite des Plugin-Marktplatzes unter "Neueste Plugins" aufgeführt.
+
+**Herzlichen Glückwunsch zum ersten veröffentlichten Stud.IP Plugin!**
+
+Moritz Strohm hat ein weiteres Tutorial zur Erstellung von Plugins
+erstellt. Dieses finden Sie hier: https://develop.studip.de/studip/dispatch.php/document/download/5747961f81b385b1520cf7dc393f1db6
diff --git a/docs/docs/quickstart/allgemeine-struktur.md b/docs/docs/quickstart/allgemeine-struktur.md
new file mode 100644
index 0000000..46ef116
--- /dev/null
+++ b/docs/docs/quickstart/allgemeine-struktur.md
@@ -0,0 +1,162 @@
+---
+title: Allgemeine Struktur
+---
+
+Jeder, der einmal eine Programmiersprache angefangen hat, weiß, womit man anfängt: mit einem hello-world Programm. Ja, man mag die Dinger nicht mehr sehen, aber nützlich, um die grobe Syntax und die minimalsten Konventionen zu verstehen, sind sie allemal.
+
+Bei Studip betrachten wir einfach mal eine komplett leere Seite, die nur einen Schiftzug in sich trägt. Um zu sehen, welchen Anteil einheitliches Design und Session-Routinen einnehmen, sollte man sich einfach klar darüber werden, wieviele Programmzeilen eine derartig "nackte" Studip-Seite klein ist.
+
+Die nun folgende Datei könnte bei Studip locker im Ordner public stehen:
+
+
+
+
+
+
+
+```php
+<?php
+/*
+test.php - Anzeige einer leeren Gerüstseite von Stud.IP
+Copyright (C) 2009 Rasmus Fuhse <ras@fuhse.org>
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+$Id: test.php 12381 2009-06-03 16:57:46Z Krassmus $
+*/
+
+//Ab hier fangen wir an, über den Code nachzudenken.
+
+//// Initialisierungen: Include-Pfad usw.
+require '../lib/bootstrap.php';
+
+//// Hier wird eine Session gestartet.
+page_open(array('sess' => 'Seminar_Session',
+ 'auth' => 'Seminar_Default_Auth',
+ 'perm' => 'Seminar_Perm',
+ 'user' => 'Seminar_User'));
+$auth->login_if($_REQUEST['again'] && ($auth->auth["uid"] == "nobody"));
+$perm->check("user");
+
+include ('lib/seminar_open.php'); // initialise Stud.IP-Session
+
+
+//// Variablen, die zur Anzeige helfen.
+$HELP_KEYWORD="Basis.Testseite"; // Wer auf Hilfe klickt, wird zur Hilfeseite Basis.Testseite geführt.
+$CURRENT_PAGE = _("Testseite"); // Zeigt an, wie diese Seite heißt
+
+
+//// Ab hier wird der erste Text in das HTML-Doc geschrieben
+
+//HTML-Header bis zur <body> Anweisungen
+include 'lib/include/html_head.inc.php';
+
+//Studip-Header, also die Navigationssymbole, die über fast jeder Seite stehen.
+include 'lib/include/header.php';
+
+
+//Hier kommt nun die eigentliche Nachricht
+$ausgabe_format = '<table class="blank" width="100%%"
+border="0" cellpadding="0" cellspacing="0">
+<tr><td class="topic"><b>&nbsp;%s </b>%s</td></tr>
+<tr><td class="steel1">&nbsp;</td></tr><tr><td class="steel1"><blockquote>%s</blockquote></td></tr>
+<tr><td class="steel1">&nbsp;</td></tr>
+</table><br>'."\n";
+
+printf ($ausgabe_format, htmlReady( _("und nun") ), *, formatReady( _("Hello World!") ));
+
+
+// Save data back to database.
+page_close();
+
+?>
+```
+
+
+# Wichtige Elemente der Testseite: Bootstrap
+[#bootstrap](#bootstrap)
+
+Das erste Statement eines Skripts in `public` lautet immer:
+
+```php
+// Initialisierungen: Include-Pfad usw.
+require '../lib/bootstrap.php';
+```
+
+Damit wird der $STUDIP_BASE_PATH gesetzt, der Include-Pfad angepasst und alle wichtigen Konfigurations- und Systemklassendateien geladen.
+
+# Wichtige Elemente der Testseite: Sessions
+[#page_open](#page_open)
+
+Bei Studip wird aus diesen oder jenen Gründen einiges anders gemacht als bei anderen PHP-Modulen. Das fängt zum Beispiel mit der Session an. Bei PHP gibt es ein eingebautes Session-Management, womit man theoretisch Variablen über das, was der Nutzer gerade macht, welche Daten er eingegeben hat und so weiter, global auf dem Server ablegen kann. Aber leider geht das erst ab PHP4. Da Studip unter PHP3 entstanden ist, wird bis heute ein Session-Management mitgeschleppt, das auf den Zusatz PHPLIB aufbaut und für moderne PHP-Entwickler etwas altbacken aussieht. Im Grunde ist es aber das selbe wie eine normale PHP-Session und lässt sich auch einfach bedienen. Auf der Testseite wird diese Session durch
+
+```php
+page_open(array('sess' => 'Seminar_Session',
+ 'auth' => 'Seminar_Default_Auth',
+ 'perm' => 'Seminar_Perm',
+ 'user' => 'Seminar_User'));
+```
+
+gestartet und durch
+
+`page_close();`
+
+wieder beendet. Beenden muss man die PHPLIB-Session, damit alle Variablen auch wirklich bei der nächsten Session (auf der nächsten Stud.IP Seite) wieder zur Verfügung stehen.
+
+# Sicherheitsüberprüfung
+
+Gleich nach dem page_open(...) folgt die Sicherheitsüberprüfung, ob der Nutzer überhaupt die Seite sehen darf. Bei
+
+`$perm->check("user");`
+
+wird zum Beispiel überprüft, ob der Betrachter der Seite die Rechte eines "user" hat. Bei einer Seite, die man nur mit Admin-Rechten sehen sollte, würde also
+
+`$perm->check("admin");`
+
+stehen.
+Es gibt fünf Sicherheitslevels: Gast (also ohne besondere Rechte, der darf nur öffentliche Veranstaltungen sehen, bei denen die Sicherheitsabfrage im Code fehlt), "user", "tutor", "dozent" und "admin". Gleich nach der Sicherheitsüberprüfung, wird in der Include-Datei
+
+`include ('lib/seminar_open.php');`
+
+die Session mit allerlei Variablen zugemüllt, die auf den meisten Seiten von Belang sind, nur auf unserer kleinen Testseite noch nicht.
+
+# Aufbau der Kopfzeilen
+
+In Studip strebt man ein einheitliches, gediegenes Design an. Dazu gehört, dass alle Seiten (nimmt man mal zum Beispiel den Messenger raus) die gleiche Kopfzeile und die gleichen Style-Anweisungen einbauen. Das geschieht in den Zeilen:
+
+`include 'lib/include/html_head.inc.php';`
+
+für das HTML-Grundgerüst von `<html>` bis zu `<body>` mitsammt allen Einbindungen von CSS-Dateien und so weiter und
+
+`include 'lib/include/header.php';`
+
+was die eigentlich sichtbare Kopfzeile darstellt mit den Icons für Startseite, Nachrichten, Homepage, dem Studip-Logo und so weiter. In der Kopfzeile taucht auch der Name der Seite auf und ein Link zur Hilfe-Seite, der zudem Informationen darüber verfügt, worüber genau eine Hilfeseite angezeigt werden soll. Beide Informationen werden in der header.php anhand von zwei Variablen gesetzt. Deswegen sollte schon VORHER im Code stehen:
+
+```php
+$HELP_KEYWORD="Basis.Testseite";
+$CURRENT_PAGE = _("Testseite");
+```
+
+# Textbausteine in Studip
+
+Was [später in dieser Hilfe](quickstart/Internationalisierung) noch genauer erklärt wird, sind die Textbausteine wie das gerade aufgetauchte
+
+```php
+_("Testseite")
+```
+
+Ein Studip-Neuling fragt sich zwangsläufig, was diese Unterstrichfunktion sein soll. Normaler Text würde es hier sicherlich auch tun. Das Problem ist gewissermaßen die Möglichkeit, dass man sich jede Studip-Seite auch auf Englisch oder theoretisch jeder anderen Sprache anzeigen lassen könnte. Die entsprechende Übersetzungsarbeit wird nicht im Code vorgenommen (was den Code noch unübersichtlicher werden lassen würde, als er ohnehin schon ist), sondern in der Deklaration der Funktion Unterstrich "_" oder auch gettext(). Deswegen gilt für die Entwickler, dass jedes bisschen Text, das nicht eine HTML-Anweisung ist, durch die _("...") Funktion gejagt wird.
+
+Als Beispiel, was genau durch gettext gesetzt werden muss und was nicht, eignet sich das Beispiel oben gut. Die Variable `$CURRENT_PAGE` wird als tatsächlich sichtbarer Text in die Kopfzeile geschrieben und die Variable `$HELP_KEYWORD` dient nur als Link-Parameter, der nicht sichtbar ist, sondern nur der Hilfe-Seite in der Adresszeile des Browsers übergeben wird.
diff --git a/docs/docs/quickstart/api-dokumentation.md b/docs/docs/quickstart/api-dokumentation.md
new file mode 100644
index 0000000..e0ff4f2
--- /dev/null
+++ b/docs/docs/quickstart/api-dokumentation.md
@@ -0,0 +1,146 @@
+---
+title: API-Dokumentation
+---
+
+# Offizielle API-Dokumentation
+
+Die offizielle API-Dokumentation kann unter der URL:
+
+https://docs.gitlab.studip.de/api
+
+eingesehen werden. Verwendet wird dafür das Werkzeug [doxygen](https://www.doxygen.nl/index.html), was (anders als phpdoc) schnell und zuverlässig aus dem Quellcode die API-Dokumentation erzeugt. In der dortigen Sidebar befinden sich folgende Auflistungen:
+
+
+| Wert | Beschreibung |
+| ---- | ---- |
+|Data Structures|vorhandene Klassen |
+|Class Hierarchy|Klassen hierarhisch nach Inheritance gruppiert |
+|Data Fields|Klassen- und Instanzvariablen |
+|File List|alle Dateien |
+|Directory Hierarchy|aufgeklappte Verzeichnisstrukturen |
+|Examples|Liste aller in den Kommentaren enthaltenen Beispiele |
+|Globals|Liste aller globalen Variablen und Funktionen |
+
+
+
+### Erzeugen der Dokumentation
+
+Im Gitlab liegt ein [Makefile](https://gitlab.studip.de/studip/studip/-/blob/main/Makefile) mit Target "doc", so dass der folgende Aufruf:
+
+`1 ~ % make doc`
+
+im Verzeichnis `doc/html` die entsprechende API-Dokumentation frisch erzeugt.
+
+Voraussetzung dafür ist die Installation von `doxygen`. Verwendet man Linux, kann man `doxygen` meist einfach über die Paketverwaltung installieren. Unter Ubuntu reicht dort zum Beispiel:
+
+`2 ~ % sudo apt-get install doxygen`
+
+Grundsätzlich kann `doxygen` auch für Unix, Mac und Windows aus den Quellen installiert werden. Eingehender dazu informiert die [englische Anleitung](https://www.doxygen.nl/manual/install.html).
+
+Die Konfiguration für die Erzeugung befindet sich in [tools/Doxyfile](https://gitlab.studip.de/studip/studip/-/blob/main/Doxyfile).
+Besonders einfach lässt sich diese mit `doxywizard` erzeugen.
+
+
+## Wie schreibe ich API-Dokumentation?
+
+Wer schon einmal mit `@phpdoc@` oder `@javadoc@` gearbeitet hat, kennt sich praktisch schon aus.
+Selbstverständlich gibt es auch noch doxygen eigene Spezifika, die [weiter unten](#besonderheiten) erläutert werden.
+Zunächst aber einige Best Practices, wie dokumentiert werden soll.
+
+
+#### Top/Datei-Level-Kommentare
+Jede PHP-Datei außerhalb von `/template` muss mit einem Copyright-Vorspann und einer Beschreibung des Inhalts der Datei eingeleitet werden:
+
+```php
+/**
+ * filename - Short description for file
+ *
+ * Long description for file (if any)...
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author name <email>
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ */
+```
+
+
+
+### Class-Kommentare
+
+Jede Klasse muss einen Docblock haben, der den Nutzen und(!) die Verwendung beschreibt.
+
+```php
+/**
+ * This class provides a singleton instance that is used to manage PDO database
+ * connections.
+ *
+ * Example of use:
+ *
+ * # getting a PDO connection
+ * $key = 'studip';
+ * $db = DBManager::get($key);
+ * $db->query('SELECT * FROM user_info');
+ *
+ * # setting a PDO connection
+ * $manager = DBManager::getInstance();
+ * $manager->setConnection('example', 'mysql:host=localhost;dbname=example',
+ * 'root', *);
+ *
+ **/
+```
+
+
+Wenn die Klasse schon ausführlich im Top-Level-Kommentar beschrieben wurde, darf man stattdessen dorthin verweisen: "für eine ausführliche Beschreibung siehe Kommentar am Anfang dieser Datei".
+
+### Methoden- und Funktionskommentare
+
+Jede Funktion und Methode muss einen Docblock haben, der beschreibt, was die Funktion/Methode tut und wie man sie verwendet.
+Die Kommentare sollten deskriptiv ("Opens the file") und nicht imperativ ("Open the file") sein. Für gewöhnlich braucht der Kommentar nicht beschreiben, %%wie%% die Funktion funktioniert.
+Solche Kommentare sollten direkt im Quelltext stehen.
+
+Die folgenden Dinge sollten im Kommentar enthalten sein:
+
+* eine Beschreibung der Funktion
+* alle Argumente und ihre Beschreibung
+* alle möglichen Rückgabewerte und ihre Beschreibung
+* ob und wann die Funktion Exceptions wirft
+
+Es müssen ganze Sätze verwendet werden. Funktionen sollten ebenso wie Klassen deskriptiv (in dritter Person) kommentiert werden.
+
+Wenn Getter/Setter-Methoden, Konstruktoren oder Destruktoren nichts unerwartetes tun, darf die Beschreibung der Funktion/Methode weggelassen werden.
+
+````phpregexp
+ /***
+ * Returns the value of the selected query parameter as a string.
+ *
+ * @param string $param parameter name
+ * @param string $default default value if parameter is not set
+ *
+ * @return string parameter value as string (if set), else NULL
+ */
+````
+
+#### Codebeispiele
+
+Codebeispiele können ganz einfach in einen Kommentar einfügt werden, indem man semantisch den Bereich mit @code und @endcode einschliesst. Beispielsweise enthält die Datei [DBManager](https://develop.studip.de/trac/browser/trunk/lib/classes/DBManager.class.php#L15) folgenden Kommentar:
+
+```php
+/**
+ * This class provides a singleton instance that is used to manage PDO database
+ * connections.
+ *
+ * Example of use:
+ * @code
+ * # get hold of the DBManager's singleton
+ * $manager = DBManager::getInstance();
+ *
+ * [...]
+ *
+ * @endcode
+ */
+```
diff --git a/docs/docs/quickstart/asset-bundling.md b/docs/docs/quickstart/asset-bundling.md
new file mode 100644
index 0000000..b04ddc4
--- /dev/null
+++ b/docs/docs/quickstart/asset-bundling.md
@@ -0,0 +1,80 @@
+---
+title: Asset Bundling
+---
+
+## Asset Bundling
+Asset Bundling ist das Zusammenstellen und nötigenfalls Kompilieren von Modulen. Module können u.a. JavaScript, CSS, LESS-CSS enthalten. Module haben Abhängigkeiten unter einander. Asset Bundling löst diese Abhängigkeiten auf, kompiliert Module falls nötig und generiert zum Beispiel fertige JS- und CSS-Dateien.
+
+Stud.IP verwendet in der Version 4.2 und aufwärts den Open-Source-Asset-Bundler "webpack".
+
+Die Konfigurationsdateien liegen im Projektverzeichnis und lauten "webpack.dev.js", "webpack.dev-server.js" und "webpack.prod.js".
+
+Damit stehen drei Modi zum Asset-Bundling zur Verfügung:
+
+* `make webpack-dev`: Schnell. Kompiliert und bundled im Developer-Modus. Die Bundles sind nicht optimiert und leicht zu debuggen.
+* `make webpack-prod`: Langsam. Kompiliert und bundled im Production-Modus. Die Bundles sind optimiert und mit Source Maps zu debuggen.
+* `make wds`: Sehr schnell. Startet den webpack-dev-server. Die $ASSETS_URL muss geändert werden: `$ASSETS_URL = "http://localhost:8123/";` (ab Stud.IP 4.4 wird die Anpassung der `$ASSETS_URL` automatisch vorgenommen, wenn der webpack-dev-server läuft).
+
+**Einmalig** vor dem ersten `make`-Aufruf müssen die npm-Pakete installiert werden:
+
+`npm install`
+
+Wenn man dann JavaScript- oder CSS-Dateien modifiziert hat, ruft man `make webpack-dev` oder `make webpack-prod` auf, damit die Änderungen in die Output-Dateien hineinkompiliert werden.
+
+### make webpack-dev vs. make webpack-prod
+
+Da `webpack-dev` einiges schneller ist (aber nicht optimiert), eignet sich `make webpack-dev` für die lokale Entwicklung.
+
+Sobald man Änderungen im SVN einchecken möchte, sollte man dringend `make webpack-prod` aufrufen.
+Da das Developer-Board direkt die Dateien aus dem `trunk` nimmt, sollten dort auch nur optimierte Dateien liegen, da sonst alle Developer-Board-Nutzer längere Ladezeiten haben.
+
+Bei `make webpack-prod` werden die Debug-Informationen in Sourcemap-Dateien hinterlegt, mit denen das Debugging ähnlich komfortabel sein sollte.
+
+Auf einem Testsystem läuft `make webpack-dev` in ~6 Sekunden durch. Die `make webpack-prod`-Variante benötigt auf dem System ~17 Sekunden.
+Am schnellsten ist aber `make wds`, das ab dem Zeitpunkt des **Abspeicherns** der Änderung weniger als 2 Sekunden benötigt
+
+
+**Wann sollte man `make webpack-dev` aufrufen?**
+
+Immer wenn man gerade nur lokal entwickelt, ohne etwas einzuchecken.
+
+**Wann sollte man `make webpack-prod` aufrufen?**
+
+Immer bevor man JS- oder CSS-Änderungen im SVN einchecken wird. Vorher sollte man aber auf jeden Fall `npm install` aufrufen, um sicher zu sein, dass man die aktuellsten Versionen der verwendeten Bibliotheken vorliegen hat.
+
+
+### make wds
+
+Mit `make webpack-prod` und `make webpack-dev` werden die Assets auf der Kommandozeile zusammengebaut. Der webpack-dev-server hingegen wacht über die Assets-Dateien; ändert sich eine davon, stößt er einen inkrementellen Build an und lädt im Browser die Seite neu.
+
+Wenn man den webpack-dev-server verwenden möchte:
+
+* ändert man zunächst in der Datei config_local.inc.php die ASSETS_URL auf: `$ASSETS_URL = "http://localhost:8123/";`
+* ruft dann auf der Kommandozeile `make wds` auf.
+
+Die Assets werden dann inkrementell zusammengebaut und über `http://localhost:8123/` ausgeliefert.
+
+Über die magische URL: `http://localhost:8123/webpack-dev-server` sieht man, was der webpack-dev-server ausliefert
+
+
+### Kryptische Namen von gebundelten JavaScript-Dateien
+
+Damit nach einem Versionswechsel einer Stud.IP-Installation keine Caching-Probleme beim Nutzer entstehen, wurde vor einigen Jahren eingeführt, dass die JavaScript-Dateien via PHP mit einem speziellen versionsabhängigen URL-Parameter eingebunden werden, der damit das Caching-Problem löst.
+
+Wenn nun JavaScript-Dateien dynamisch geladen werden (siehe [JavaScript in Stud.IP](HowToJavascript)), werden die nachzuladenden Dateien direkt über JS eingebunden, ohne den Umweg über PHP zu nehmen. Damit kann man also den vorhandenen PHP-Mechanismus nicht mehr verwenden. Das Problem wird in ticket:9114 berichtet. Die nachzuladenden JS-Dateien werden als "chunks" bezeichnet.
+
+Glücklicherweise bietet webpack zu diesem Zweck die Konfigurationsmöglichkeit, [den Dateinamen der nachzuladenden JS-Dateien ("chunks") anzupassen](https://webpack.js.org/configuration/output/#output-chunkfilename):
+
+```shell
+output: {
+ chunkFilename: "javascripts/[name]-[chunkhash].chunk.js",
+}
+```
+
+Beim Asset-Bundling bekommen damit die "chunks" einen Namen, der einen Hash-Wert über den Inhalt des Chunks enthält.
+Ändert sich der Chunk inhaltlich bekommt er einen neuen Namen. An den Stellen im JS-Code, an denen auf diesen "chunk" verwiesen wird, setzt webpack automatisch diesen inhaltabhängigen Namen ein. Das ganze funktioniert natürlich automatisch, sodass man als Entwickler keine Mühe damit hat.
+
+**ABER:** Ändert man den "chunk" oder updatet (automatisch) 3rd-party-Bibliotheken, die im "chunk" verwendet werden, ändert sich auch der Name der gebundelten "chunk"-Datei. Ruft man nach so einer Änderung "make" oder "webpack […]" auf, entstehen neue "*.chunk.js" Dateien. Wenn man diese Dateien in eine Versionsverwaltung (svn oder git) einchecken möchte, muss man dann zuvor die alten "chunk"-Dateien abräumen und die neuen "chunk"-Dateien hinzufügen. **Dies ist aber lediglich dann erforderlich, wenn man an diesen Dateien Änderungen vornimmt.**
+
+Damit kann es also passieren, dass Änderungen am Tablesorter durch Caching nicht unmittelbar beim Nutzer ankommen. Dieses Problem habe ich in ticket:9114 berichtet.
+
diff --git a/docs/docs/quickstart/buttons.md b/docs/docs/quickstart/buttons.md
new file mode 100644
index 0000000..32f1c61
--- /dev/null
+++ b/docs/docs/quickstart/buttons.md
@@ -0,0 +1,38 @@
+---
+id: buttons
+title: Buttons
+sidebar_label: Buttons
+---
+
+Buttons werden in Stud.IP über eine eigene Klasse erzeugt, die im [Studip-Namespace](Studip-Namespace) vorliegt. Abgeleitet sind sie von der Klasse Interactable, deren Beschreibung unter folgender URL verfügbar ist:
+https://hilfe.studip.de/api/class_studip_1_1_interactable.html
+
+Vor allem bei der Entwicklung von Views sind Buttons sehr nützlich.
+
+### Arten von Buttons
+
+#### Button
+
+Dies bezeichnet einen einfachen Button, welcher als `<button>`-Element in HTML dargestellt wird. Im einfachsten Fall wird nur eine Beschriftung, ein Name und ein Array von Attributen benötigt, um einen Button zu erzeugen. Hier wird beispielsweise in einer Ansicht (View) ein einfacher Button erzeugt:
+
+```php
+<?= \Studip\Button::create('Klick mich!', 'klickMichButton', ['data-dialog-button' => '1', 'data-hallo' => 'welt']); ?>
+```
+
+#### LinkButton
+
+Ein LinkButton wird im Gegensatz zum einfachen Button als `<a>`-Element (Verweis) in HTML dargestellt. Bei der Erzeugung wird aber die gleiche statische Methode verwendet. Dort, wo im Standard-Button der Name eingetragen wird, wird beim LinkButton die aufzurufende URL eingetragen.
+
+```php
+<?= \Studip\LinkButton::create('Klick mich!', 'http://example.org', ['data-dialog' => '1', 'data-hallo' => 'welt']); ?>
+```
+
+Der zweite Parameter gibt statt eines Namens eine URL an, die beim Klick besucht werden soll. Natürlich kann hier auch die URL für einen Stud.IP-Controller oder den Controller eines Plugins angegeben werden.
+
+#### ResetButton
+
+Ein ResetButton ist besonders in HTML-Formularen nützlich, da er in HTML als `<input>`-Element vom Typ "reset" gezeichnet wird, sodass er beim Klick ein Formular zurücksetzt.
+
+```php
+<?= \Studip\ResetButton::create('Klick mich!', 'klickMichButton', ['data-dialog-button' => '1', 'data-hallo' => 'welt']); ?>
+```
diff --git a/docs/docs/quickstart/cheat-sheet.md b/docs/docs/quickstart/cheat-sheet.md
new file mode 100644
index 0000000..fe40c8d
--- /dev/null
+++ b/docs/docs/quickstart/cheat-sheet.md
@@ -0,0 +1,310 @@
+---
+id: cheat-sheet
+title: Cheat-Sheet
+sidebar_label: Cheat-Sheet
+---
+
+## User
+
+### Aktuellen Benutzer finden
+```php
+User::findCurrent();
+```
+
+### Wert aus der Benutzer-Konfiguration auslesen
+
+```php
+$wert = UserConfig::get(User::findCurrent()->id)->getValue('WERT_NAME');
+```
+
+### Wert in der Benutzer-Konfiguration speichern
+
+```php
+UserConfig::get(User::findCurrent()->id)->store('WERT_NAME', $wert);
+```
+
+WICHTIG: `$wert` wird standardmäßig als String gespeichert, sofern nichts anderes in der Config-Tabelle angegeben ist!
+Arrays sollten vorher mit `json_encode($array);` kodiert werden!
+
+### Prüfen, ob ein Benutzer Administrator ist
+
+```php
+$GLOBALS['perm']->have_perm('admin')
+```
+
+Liefert true zurück, wenn der Benutzer entweder 'root'- oder 'admin'-Berechtigungen hat, ansonsten false.
+
+
+## SimpleORMap
+
+### Veranstaltungen
+
+Courses (/lib/models/Course.class.php)
+
+### Archivierte Veranstaltungen
+
+ArchivedCourses (/lib/models/ArchivedCourse.class.php)
+
+### Dozenten
+
+User-Klasse, alle Objekte, bei welchen das Attribut "perms" auf "dozent" gesetzt ist.
+
+### Benutzer
+
+User (/lib/models/User.class.php), Erweiterung von AuthUserMd5, welche die Grunddaten (z.B. Vorname, Nachname, Benutzername) eines Benutzers enthält.
+
+## Controller-relevante Klassen
+
+### URLs
+
+#### URL erzeugen
+
+```php
+URLHelper::getLink('dispatch.php/CONTROLLER);
+```
+wobei CONTROLLER der aufzurufende Controller ist.
+
+**WICHTIG**: `getLink` ändert URLS so ab, sodass diese in HTML-Code eingebettet werden können.
+Beispielsweise wird aus & ein &amp;. Will man diese Umwandlung nicht, sollte `URLHelper::getURL();` verwendet werden.
+
+#### URL-Erzeugung im Plugin
+
+```php
+PluginEngine::getLink(PLUGIN, PARAMETER, PFAD);
+```
+
+`PLUGIN = $this->plugin` (im Controller) oder `$plugin` (im Template), PARAMETER = assoziatives Array, PFAD = Pfad zum Controller)
+
+**WICHTIG**: getLink ändert URLS so ab, sodass diese in HTML-Code eingebettet werden können. Beispielsweise wird aus & ein &amp;.
+Will man diese Umwandlung nicht, sollte `PluginEngine::getURL();` verwendet werden.
+
+Sobald man sich in einer Trails-App befindet, sollte allerdings die Controller-Methode `url_for()` genutzt werden, welche den obigen Aufruf kapselt und vereinfacht.
+
+#### Icon-Erzeugung
+
+```php
+Icon::create(SYMBOL, KATEGORIE)->asImg(GRÖßE);
+```
+* SYMBOL = das anzuzeigende Symbol
+* KATEGORIE = farbliche Einordnung des Symbols (z.B. `Icon::ROLE_CLICKABLE`),
+* GRÖßE = Angabe in Pixeln (z.B. "12px")
+
+#### URL-Parameter "dauerhaft" machen
+
+Um einen Parameter beim Aufruf der nächsten Seite mitgeben zu können, verwendet man die Methode addLinkParam von URLHelper:
+
+`URLHelper::addLinkParam('name', WERT);`
+
+An die URL wird nun der Parameter name mit dem Wert WERT angehängt (z.B.: `http://example.org?name=WERT`).
+
+## Darstellung
+
+#### Navigationselemente
+
+##### Navigationselement auf der Startseite hinzufügen
+
+```php
+<?php
+$navigation = new Navigation('LINKBESCHREIBUNG', 'EINE_URL'); //Erzeugen des Navigationselementes mit einem passenden Text und der gewünschten URL
+Navigation::addItem('/start/ID', $navigation); //ID = eindeutige Bezeichnung des Navigationselementes. /start/ muss auf jeden Fall dorthin, damit das Element auf der Startseite angezeigt wird
+```
+
+##### Reiternavigation (Tabs) erzeugen
+
+```php
+<?php
+$navigation = new Navigation('LINKBESCHREIBUNG', 'EINE_URL');
+Navigation::addItem('/ID', $navigation); //ID = eindeutiger Pfad. Dieser liegt in der "Wurzel", also nicht unterhalb anderer Pfade wie z.B. /start/
+```
+
+In dem Controller, welcher über obiges Navigationselement erreichbar ist, muss das Navigationselement aktiviert werden:
+```php
+<?php
+Navigation::activateItem('/ID');
+```
+
+**Hinweis:** Natürlich sind auch hier Unterpfade, z.B. /ID/NOCHEINEID möglich.
+
+##### Icon in Reiternavigation einfügen
+
+Beim Hinzufügen von Icons in die Reiternavigation muss beachtet werden, dass das Icon eines aktiven Reiters eine andere Farbe hat als das Icon eines inaktiven Reiters. Deshalb muss der Wechsel des Icons bei der Aktivierung eines Reiters durchgeführt werden.
+
+Definition des Reiters in der Navigationsstruktur:
+```php
+<?php
+$navigation = new Navigation(
+ 'Text',
+ PluginEngine::getUrl('ein/link')
+ );
+$navigation->setImage(Icon::create('edit', Icon::ROLE_NAVIGATION));
+Navigation::addItem('/navigations/pfad', $navigation);
+```
+
+#### Hinweistexte
+
+Möglichkeiten für Hinweistexte:
+
+| Typ | Beschreibung |
+| ---- | ---- |
+| `MessageBox::error` | Fehlermeldungen |
+| `MessageBox::info` | Informationen |
+| `MessageBox::warning` | Warnmeldungen (aber keine Fehler) |
+| `MessageBox::success` | Erfolgsbestätigungen (Aktionen, die erfolgreich abgeschlossen wurden) |
+
+
+Mehr Informationen: [Messagebox](MessageBox)
+
+##### Hinweistexte vom Controller heraus ausgeben
+
+```php
+<?php
+PageLayout::postError(_('Fehler!'));
+```
+
+Mehr zu PageLayout: [PageLayout](PageLayout)
+
+#### Sidebar
+
+##### Navigations-Bereich hinzufügen
+
+```php
+<?php
+$navigation = new NavigationWidget();
+$navigation->setTitle('Titel des Bereiches');
+
+//hier wird ein Link hinzugefügt:
+$navigation->addLink(
+ 'Ein Linktitel',
+ PluginEngine::getURL($this->plugin, [], 'show')
+);
+
+Sidebar::Get()->addWidget($navigation); //Navigations-Bereich in die Sidebar einhängen
+```
+
+
+##### Link auf Dialog im ActionsWidget hinzufügen
+
+```php
+<?php
+$actions = new ActionsWidget();
+$actions->addLink(
+ 'Beschreibung',
+ URLHelper::getURL('dispatch.php/CONTROLLER'),
+ Icon::create(SYMBOL, KATEGORIE)
+)->asDialog();
+```
+
+CONTROLLER ist der aufzurufende Controller, SYMBOL das ausgewählte Symbol, dessen Farbe durch die Kategorie KATEGORIE gesetzt wird. Mit der Methode asDialog() (Klasse LinkElement in /lib/classes/sidebar) wird das HTML-Attribut "data-dialog" beim Erstellen des HTML-Codes des Links gesetzt.
+
+##### Schnellsuche (Suche mit Drop-Down-Menü) zu einem Suchfeld der Sidebar hinzufügen
+
+```php
+<?php
+$searchWidget = new SearchWidget(PluginEngine::getLink($this->plugin, [], 'search'));
+$searchWidget->setTitle(_('Suche'));
+$searchWidget->setMethod('post');
+
+$sqlSearch = new SQLSearch("SELECT auth_user_md5.user_id as userId FROM auth_user_md5 " .
+ "WHERE ((vorname like CONCAT('%', :input, '%') " .
+ "OR (nachname like CONCAT('%', :input, '%')) ",
+ _('Benutzername')
+ );
+
+//QuickSearch zum SearchWidget hinzufügen:
+$searchWidget->addNeedle(
+ _('Benutzername'),
+ 'userId',
+ _('Benutzername'),
+ $sqlSearch
+ );
+```
+
+Mehr zu QuickSearch: [QuickSearch](QuickSearch)
+
+
+#### Templates
+
+##### Button erzeugen
+
+```php
+<?= \Studip\Button::create(_('Speichern')); ?>
+```
+
+##### Button für Dialog erzeugen
+
+```php
+<div data-dialog-button>
+ <?= \Studip\Button::create(_('Speichern')); ?>
+</div>
+```
+
+
+### Plugins
+
+#### Herausfinden, ob ein anderes Plugin aktiviert ist
+
+Dies ist z.B. sinnvoll, wenn ein Plugin von einem anderen Plugin oder dessen Klassen abhängig ist. Folgender Code prüft, ob ein anderes Plugin aktiviert ist:
+```php
+$pluginManager = PluginManager::getInstance();
+$pluginManager->getPluginInfo('AnderesPlugin');
+//$pluginManager enthält nun Daten über das gesuchte andere Plugin.
+if ($pluginManager['enabled']) {
+ //das andere Plugin ist angeschaltet: Nun können z.B. Klassen dieses Plugins eingebunden werden oder andere Dinge gemacht werden, die dieses Plugin voraussetzen
+}
+```
+
+#### Alle Plugins für eine Seite deaktivieren
+
+Um festzustellen, ob Probleme auf einer Seite von einem Plugin ausgelöst werden, können auf dieser Seite alle Plugins deaktiviert werden, indem der URL-Parameter `disable_plugins=1` angehängt wird.
+
+
+### JavaScript
+
+#### foreach in JavaScript
+
+```javascript
+for (var element of someArray) {
+ doSomething(element);
+}
+```
+
+Siehe: [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of)
+
+#### JavaScript-Datei eines Plugins einbinden
+
+In der Plugin-Klasse wird im Konstruktor folgender Code eingefügt, falls das JavaScript auf allen Seiten verfügbar sein soll. Ansonsten muss der folgende Code in der `perform()`-Methode des Plugins eingefügt werden, damit das JavaScript nur auf den Pluginseiten zur Verfügung steht:
+```php
+<?php
+
+PageLayout::addScript($this->getPluginURL() . '/assets/javascript/JavaScriptDatei.js');
+```
+
+Mehr zu PageLayout: [PageLayout](PageLayout)
+
+#### URL in JavaScript erzeugen
+
+In JavaScript ist ebenfalls ein URLHelper implementiert, welcher sich ähnlich aufrufen lässt, wie der URLHelper in PHP:
+
+`STUDIP.URLHelper.getURL(URL, {"parameter" : WERT});`
+
+Das Objekt, welches in JSON-Notation hinter der URL angegeben wird, beinhaltet Parameter, welche an die URL angehängt werden.
+
+### Composer
+
+#### Wie installiere ich die durch Composer definierten Abhängigkeiten?
+
+`composer install` bzw. `make composer`
+
+#### Wie installiere ich eine neue Abhängigkeit mittels Composer?
+
+`composer require <lib>`
+
+#### Wie aktualisiere ich eine durch Composer definierte Abhängigkeit?
+
+`composer update <lib>`
+
+Es dürfen immer nur einzelne Abhängigkeiten im Rahmen eines TICs (oder Bugfixes, wenn nötig) aktualisiert werden, da durch das Update durch API-Änderungen oder andere kritische Änderungen Probleme entstehen können. Es sollte niemals grundlos `composer update` ohne Angabe einer Lib aufgerufen werden.
+
+#### Wo finde ich weitere Informationen, wie man Composer verwendet?
+
+[https://getcomposer.org/doc/01-basic-usage.md](https://getcomposer.org/doc/01-basic-usage.md)
diff --git a/docs/docs/quickstart/coding-style.md b/docs/docs/quickstart/coding-style.md
new file mode 100644
index 0000000..970fede
--- /dev/null
+++ b/docs/docs/quickstart/coding-style.md
@@ -0,0 +1,525 @@
+## Stud.IP Coding Standard
+
+### Geltungsbereich
+
+Dieses Dokument bietet Richtlinien für die Formatierung von Code und Dokumentation für Entwickler, die an Stud.IP mitarbeiten. Die folgenden Bereiche werden vom Stud.IP Coding Standard abgedeckt:
+
+
+## PHP Dateiformatierung
+
+Für Dateien, die nur PHP Code beinhalten ist der schliessende Tag ("?>") nicht zugelassen. Er wird von PHP nicht benötigt, und das weglassen verhindert, dass versehentlich Leerzeilen in die Antwort eingefügt werden.
+
+**WICHTIG:** Einbeziehen von beliebigen binären Daten durch __HALT_COMPILER() ist in den PHP Dateien verboten. Das Benutzen ist nur für einige Installationsskripte erlaubt.
+
+## Einrücken
+
+Ein Einzug sollte aus 4 Leerzeichen bestehen. Tabulatoren sind nicht erlaubt.
+
+## Maximale Zeilenlänge
+
+Die Zielzeilenlänge ist 80 Zeichen. Entwickler sollten jede Zeile Ihres Codes unter 80 Zeichen halten, soweit dies möglich und praktikabel ist. Trotzdem sind längere Zeilen in einigen Fällen erlaubt. Die maximale Länge einer Zeile beträgt 120 Zeichen.
+
+## Zeilenbegrenzung
+
+Die Zeilenbegrenzung folgt der Unix-Textdateikonvention. Zeilen müssen mit einem einzelnen Zeilenvorschubzeichen (LF) enden. Zeilenvorschubzeichen werden duch eine 10 (dezimal) bzw. durch 0x0A (hexadezimal) dargestellt.
+
+Beachte: Benutzen Sie nicht den Wagenrücklauf (CR &#8594; 0x0D) oder die Kombination aus Wagenrücklauf und Zeilenvorschub (CRLF &#8594; 0x0D 0x0A).
+
+
+## Namenskonventionen
+
+### Klassen
+
+Klassennamen dürfen nur alphanumerische Zeichen enthalten. Nummern sind in Klassennamen gestattet, es wird aber in den meisten Fällen davon abgeraten.
+
+Wenn ein Klassenname aus mehr als einem Wort besteht, muß der erste Buchstabe von jedem neuen Wort großgeschrieben werden.
+
+Um Pseudo-Namensräume zu definieren, dürfen in Klassennamen einzelne Unterstriche verwendet werden.
+
+Beispiel: `class Trails_Controller`
+
+Sobald echte Namensräume verfügbar sind, müssen diese Pseudo-Namensräume entsprechend ersetzt werden.
+
+### Dateinamen
+
+In Dateinamen sind nur alphanumerische Zeichen ("a-zA-Z0-9"), Unterstriche ("_"), Bindestriche ("-") und Punkte (".") gestattet. Leerzeichen sind völlig verboten.
+
+Jede Datei die PHP-Code enthält, sollte mit der Endung ".php" enden.
+
+Dateinamen müssen den Klassennamen wie oben beschrieben entsprechen.
+
+### Funktionen und Methoden
+
+Methodennamen dürfen nur alphanumerische Zeichen enthalten. Unterstriche sind nicht gestattet. Ziffern sind in Funktionsnamen gestattet, aber in den meisten Fällen nicht empfohlen.
+
+Funktions- und Methodennamen müssen immer mit einem Kleinbuchstaben anfangen. Wenn ein Methodenname aus mehr als einem Wort bestehen, muß der erste Buchstabe eines jeden Wortes großgeschrieben werden. Das wird üblicherweise "camelCase"-Formatierung genannt.
+
+Wortreichtum wird generell befürwortet. Funktionsnamen sollten so wortreich wie möglich sein, um deren Zweck und Verhalten zu erklären.
+
+Beispiele für Methodenamen:
+
+```php
+filterInput()
+
+getElementById()
+
+widgetFactory()
+```
+
+Für objekt-orientiertes Programmieren sollten Zugriffsmethoden für Instanz- oder Klassenvariablen immer mit `get` oder `set` beginnen. Wenn Design-Pattern implementiert werden sollte, sollte der Name der Methode den Konventionen des Patterns entsprechen, um das Verhalten besser zu beschreiben.
+
+Globale Funktionen sind gestattet, aber es wird von ihnen in den meisten Fällen abgeraten. Diese Funktionen sollten in einer statischen Klasse gekapselt werden.
+
+### Variablen
+
+Variablennamen dürfen nur alphanumerische Zeichen und den Unterstrich enthalten. Ziffern sind in Variablen gestattet, in den meisten Fällen aber nicht empfohlen.
+
+Wie bei Funktionsnamen (siehe oben) müssen Variablennamen immer mit einem Kleinbuchstaben anfangen.
+
+Sprechende Bezeichner werden generell befürwortet. Variablen sollen immer so wortreich wie möglich sein, um die Daten zu beschreiben, die der Entwickler in ihnen zu speichern gedenkt. Von sehr kurzen Variablennamen wie `$i` und `$n` wird abgesehen von der Verwendung in kleinen Schleifen abgeraten. Wenn eine Schleife mehr als 20 Codezeilen enthält, sollten die Index-Variablen einen ausführlicheren Namen haben.
+
+### Konstanten
+
+Konstantenbezeichner können alphanumerische Zeichen und Unterstriche enthalten.
+
+Alle Buchstaben, die in Konstantenname verwendet werden, müssen großgeschrieben werden. Wörter in einem Konstantennamen müssen durch Unterstriche getrennt werden.
+
+Beispiel: `EMBED_SUPPRESS_EMBED_EXCEPTION` ist gestattet,`EMBED_SUPPRESSEMBEDEXCEPTION` jedoch nicht.
+
+Konstanten müssen als Klassenkonstanten (Schlüsselwort "const") definiert werden. Die Definition von Konstanten mit der `define` Funktion im globalen Bereich ist gestattet, jedoch wird davon stark abgeraten.
+
+
+## PHP Code-Abgrenzung
+
+PHP Code muß immer mit der kompletten Form des Standard-PHP Tags abgegrenzt sein:
+
+```php
+<?php
+
+?>
+```
+
+Kurze Tags sind nur in Templates erlaubt. Für Dateien die nur PHP Code enthalten, darf das schließende Tag nie angegeben werden.
+
+## Strings
+### String-Literale
+
+Bei String-Literalen, wenn ein String also keine Variablen enthält, sollte immer das Apostroph "'" (single quote) verwendet werden um den String abzugrenzen:
+
+```php
+$aString = 'Example String';
+```
+
+
+## String-Literale mit Apostrophen
+
+Wenn ein String-Literal selbst Apostrophe enthält, ist es gestattet den String mit Anführungszeichen (double quotes) abzugrenzen. Das ist speziell für SQL-Anweisungen nützlich:
+
+```php
+$sql = "SELECT `id`, `name` from `people` "
+ . "WHERE `name`='Fred' OR `name`='Susan'";
+```
+
+Diese Syntax ist gegenüber dem Schützen des Apostrophs durch "\'" aus Gründen der besseren Lesbarkeit zu bevorzugen.
+
+### Variable Substitution
+
+Variable substitution is permitted using either of these forms:
+
+```php
+$greeting = "Hello $name, welcome back!";
+
+$greeting = "Hello {$name}, welcome back!";
+```
+
+
+
+For consistency, this form is not permitted:
+
+```php
+$greeting = "Hello ${name}, welcome back!";
+```
+
+
+## String-Konkatenation
+
+Strings müssen mit dem "."-Operator konkateniert werden. Ein Leerzeichen muß immer vor und nach dem "." Operator eingefügt werden, um die Lesbarkeit zu erhöhen:
+
+```php
+$company = 'Zend' . ' ' . 'Technologies';
+```
+
+Werden Strings mit dem "." Operator konkateniert, sollte die Anweisung in mehrere Zeilen umgebrochen werden, um die Lesbarkeit zu erhöhen. In diesen Fällen sollte jede folgende Zeile mit Leerraum aufgefüllt werden so das der "." Operator genau unterhalb des "=" Operators steht:
+
+```php
+$sql = "SELECT `id`, `name` FROM `people` "
+ . "WHERE `name` = 'Susan' "
+ . "ORDER BY `name` ASC ";
+```
+
+
+## Arrays
+### Numerisch indizierte Arrays
+
+Negative Indizes sind nicht gestattet. Ein solches Array darf mit einer nicht-negativen Zahl beginnen, es wird jedoch davon abgeraten.
+
+Werden indizierte Arrays mehrzeilig mit Hilfe der "array"-Funktion definiert, muß ein Leerzeichen nach jeder Kommabegrenzung folgen, um die Lesbarkeit zu erhöhen:
+
+```php
+$sampleArray = array(1, 2, 3, 'Zend', 'Studio');
+```
+
+Es ist gestattet, mehrzeilige indizierte Arrays mit der "array"-Funktion zu definieren. In diesem Fall, muß jede folgende Zeile mit Leerzeichen aufgefüllt werden so das der Beginn jeder Zeile ausgerichtet ist:
+
+```php
+$sampleArray = array(1, 2, 3, 'Zend', 'Studio',
+ $a, $b, $c,
+ 56.44, $d, 500);
+```
+
+
+### Assoziative Arrays
+
+Wenn assoziative Arrays mit der "array"-Funktion deklariert werden, ist das Umbrechen der Anweisung in mehrere Zeilen gestattet. In diesem Fall muß jede folgende Linie mit Leerraum aufgefüllt werden so das beide, der Schlüssel und der Wert, untereinander stehen:
+
+```php
+$sampleArray = array(
+ 'firstKey' => 'firstValue',
+ 'secondKey' => 'secondValue'
+);
+```
+
+
+## Klassen
+### Klassendeklaration
+
+Klassen müssen den Namenskonventionen entsprechend benannt werden.
+
+Die Klammer sollte immer in der Zeile unter dem Klassennamen geschrieben werden.
+
+Jede Klasse muß einen Dokumentationsblock haben der dem PHPDocumentor Standard entspricht.
+
+Jeder Code in der Klasse muß mit vier Leerzeichen eingerückt sein.
+
+Nur eine Klasse ist in jeder PHP Datei gestattet.
+
+Das Platzieren von zusätzlichem Code in Klassendateien ist gestattet, aber es wird davon abgeraten.
+
+Das folgende ist ein Beispiel einer gültige Klassendeklaration:
+
+```php
+/**
+ * Documentation Block Here
+ */
+class SampleClass
+{
+ // all contents of class
+ // must be indented four spaces
+}
+```
+
+
+### Klassenvariablen
+
+Klassenvariablen müssen entsprechend den Variablennamenskonventionen benannt werden.
+
+Jede Variable die in der Klasse deklariert wird muß am Beginn der Klasse aufgelistet werden, vor der Deklaration von allen Methoden.
+
+Das "var"-Schlüsselwort ist nicht gestattet. Klassenvariablen definieren ihre Sichtbarkeit durch die Verwendung der private, protected, oder public Modifikatoren. Öffentliche Klassenvariable (Sichtbarkeit "public") sind erlaubt, es wird aber zu Gunsten von Zugriffsmethoden (getter/setter) davon abgeraten.
+
+## Funktionen und Methoden
+### Deklaration von Funktionen und Methoden
+
+Funktionen müssen nach der Funktionsnamenskonvention benannt werden.
+
+Methoden innerhalb von Klassen müssen immer ihre Sichtbarkeit durch Verwendung eines der private, protected, oder public Modifikatoren definieren.
+
+Wie bei Klassen sollte die Klammer immer in der Zeile unterhalb des Funktionsnamens geschrieben werden. Leerzeichen zwischen dem Funktionsnamen und der öffnenden Klammer für die Argumente sind nicht erlaubt.
+
+Von globalen Funktionen wird abgeraten.
+
+Das folgende ist ein Beispiel einer gültigen Funktionsdeklaration in einer Klasse:
+
+```php
+/**
+ * Documentation Block Here
+ */
+class Foo
+{
+ /**
+ * Documentation Block Here
+ */
+ public function bar()
+ {
+ // all contents of function
+ // must be indented four spaces
+ }
+}
+```
+
+NOTE: Pass-by-reference is the only {+explicit+} parameter passing mechanism permitted in a method declaration.
+
+```php
+/**
+ * Documentation Block Here
+ */
+class Foo
+{
+ /**
+ * Documentation Block Here
+ */
+ public function bar(&$baz)
+ {}
+}
+```
+
+Call-time pass-by-reference ist strikt verboten.
+
+Der Rückgabewert darf nicht in Klammern stehen. Das kann die Lesbarkeit behindern und zusätzlich zu Fehlern führen, wenn eine Methode später auf Rückgabe durch Referenz geändert wird.
+
+```php
+/**
+ * Documentation Block Here
+ */
+class Foo
+{
+ /**
+ * WRONG
+ */
+ public function bar()
+ {
+ return($this->bar);
+ }
+
+ /**
+ * RIGHT
+ */
+ public function bar()
+ {
+ return $this->bar;
+ }
+}
+```
+
+
+### Aufruf von Funktionen and Methoden
+
+Wie bei Funktionsdeklaration darf zwischen Funktionsnamen und der öffnenden Klammer für die Argumente beim Funktionsaufruf kein Leerzeichen stehen.
+
+Funktionsargumente sollten durch ein einzelnes trennendes Leerzeichen nach dem Komma getrennt werden. Das folgende ist ein Beispiel für einen gültigen Aufruf einer Funktion die drei Argumente benötigt:
+
+```php
+threeArguments(1, 2, 3);
+```
+
+Call-time pass-by-reference is strictly prohibited.
+
+In passing arrays as arguments to a function, the function call may include the "array" hint and may be split into multiple lines to improve readability. In such cases, the normal guidelines for writing arrays still apply:
+
+```php
+threeArguments(array(1, 2, 3), 2, 3);
+
+threeArguments(array(1, 2, 3, 'Zend', 'Studio',
+ $a, $b, $c,
+ 56.44, $d, 500), 2, 3);
+```
+
+
+## Kontrollstrukturen
+### if/else/elseif
+
+Control statements based on the if and elseif constructs must have a single space before the opening parenthesis of the conditional and a single space after the closing parenthesis.
+
+Within the conditional statements between the parentheses, operators must be separated by spaces for readability. Inner parentheses are encouraged to improve logical grouping for larger conditional expressions.
+
+The opening brace is written on the same line as the conditional statement. The closing brace is always written on its own line. Any content within the braces must be indented using four spaces.
+
+```php
+if ($a != 2) {
+ $a = 2;
+}
+```
+
+
+
+For "if" statements that include "elseif" or "else", the formatting conventions are similar to the "if" construct. The following examples demonstrate proper formatting for "if" statements with "else" and/or "elseif" constructs:
+
+```php
+if ($a != 2) {
+ $a = 2;
+} else {
+ $a = 7;
+}
+
+if ($a != 2) {
+ $a = 2;
+} elseif ($a == 3) {
+ $a = 4;
+} else {
+ $a = 7;
+}
+```
+
+
+PHP allows statements to be written without braces in some circumstances. This coding standard makes no differentiation - all "if", "elseif" or "else" statements must use braces.
+
+Use of the "elseif" construct is permitted but strongly discouraged in favor of the "else if" combination.
+
+### Switch
+
+Control statements written with the "switch" statement must have a single space before the opening parenthesis of the conditional statement and after the closing parenthesis.
+
+All content within the "switch" statement must be indented using four spaces. Content under each "case" statement must be indented using an additional four spaces.
+
+```php
+switch ($numPeople) {
+ case 1:
+ break;
+
+ case 2:
+ break;
+
+ default:
+ break;
+}
+```
+
+
+The construct default should never be omitted from a switch statement.
+
+NOTE: It is sometimes useful to write a case statement which falls through to the next case by not including a break or return within that case. To distinguish these cases from bugs, any case statement where break or return are omitted should contain a comment indicating that the break was intentionally omitted.
+
+## Inline Documentation
+### Documentation Format
+
+All documentation blocks ("docblocks") must be compatible with the phpDocumentor format. Describing the phpDocumentor format is beyond the scope of this document. For more information, visit: http://phpdoc.org/
+
+All class files must contain a "file-level" docblock at the top of each file and a "class-level" docblock immediately above each class. Examples of such docblocks can be found below.
+
+### Files
+
+Every file that contains PHP code must have a docblock at the top of the file that contains these phpDocumentor tags at a minimum:
+
+```php
+/**
+ * Short description for file
+ *
+ * Long description for file (if any)...
+ *
+ * LICENSE: Some license information
+ *
+ * @author Vorname Nachname <email>
+ * @copyright 2008 Zend Technologies
+ * @license http://framework.zend.com/license BSD License
+ * @category Stud.IP
+*/
+```
+
+Optional tags:
+```php
+/**
+ * @package calendar
+ * @link http://framework.zend.com/package/PackageName
+ * @since File available since Release 1.5.0
+*/
+```
+
+### Classes
+
+Every class must have a docblock that contains {-these phpDocumentor tags-} at a minimum:
+
+```php
+/**
+ * Short description for class
+ *
+ * Long description for class (if any)...
+ *
+ */
+```
+
+Optional tags:
+```php
+/**
+ * @link http://framework.zend.com/package/PackageName
+ * @since Class available since Release 1.5.0
+ * @deprecated Class deprecated in Release 2.0.0
+ */
+```
+
+### Functions
+
+Every function, including object methods, must have a docblock that contains at a minimum:
+
+A description of the function
+
+All of the arguments
+
+All of the possible return values
+
+
+It is not necessary to use the "@access" tag because the access level is already known from the "public", "private", or "protected" modifier used to declare the function.
+
+If a function/method may throw an exception, use @throws for all known exception classes:
+
+```php
+@throws exceptionclass [description]
+```
+
+
+## Templates
+
+Für Templates gelten alle obigen Aussagen. Zusätzlich gelten aber folgende Regeln:
+
+Bei Short-Tag-Zuweisungen muss nach dem eröffnenden und vor dem schließenden Tag genau ein Leerzeichen eingefügt werden:
+
+```php
+<div class="<?= $css_class ?>"></div>
+```
+
+Semikola werden nicht verwendet.
+
+Zur Steigerung der Lesbarkeit können die alternativen Kontrollstrukturen verwendet werden:
+
+```php
+<? if (true) : ?>
+...
+<? else : ?>
+...
+<? endif ?>
+
+<? foreach ($array() as $key => $value) : ?>
+...
+<? endforeach ?>
+
+usw.
+```
+
+Dabei ist zu beachten, dass die Doppelpunkte mit je einem Leerzeichen umschlossen werden. Die abschließenden `endif`, `endforeach` usw. dürfen (genau wie bei den sonst üblichen {}) nicht mit einem Semikolon beendet werden.
+
+
+
+### Ziele
+
+Coding Standards sind in jedem Softwareprojekt wichtig, insbesondere wenn viele Entwickler daran arbeiten. Coding Standards helfen sicherzustellen, dass der Code von hoher Qualität ist, weniger Fehler hat und einfach zu warten ist.
+
+
+### Pagelevel-Doc-Block für copy&paste
+
+Dieser Absatz ist nicht-normativ.
+
+```php
+/**
+ * filename - Short description for file
+ *
+ * Long description for file (if any)...
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author name <email>
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ */
+```
diff --git a/docs/docs/quickstart/csrf-protection.md b/docs/docs/quickstart/csrf-protection.md
new file mode 100644
index 0000000..27f7a82
--- /dev/null
+++ b/docs/docs/quickstart/csrf-protection.md
@@ -0,0 +1,79 @@
+---
+id: csrf-protection
+title: CSRFProtection - Schutz vor Cross-Site-Request-Forgeries
+sidebar_label: CSRFProtection
+---
+
+CSRF (oder auch XSRF) steht für "Cross-Site Request Forgery"—also in etwa seitenübergreifende Aufrufmanipulation. Diese Angriffsmethode funktioniert so, dass dem Nutzer schadhafter Code oder ein Link untergeschoben wird, der dann mit den Rechten des Nutzers eigentlich unauthorisierte Befehle ausführt (wie zum Beispiel das Absenden von kompromittierenden Nachrichten).
+
+Einfache Beispiele lassen sich zum Beispiel bei [Wikipedia](http://de.wikipedia.org/wiki/CSRF#Beispiele) finden.
+
+Vor zwei Jahren hatte data-quest einen entsprechenden Anlauf getätigt, der letztlich u.a. im Einbau des Bilder-Proxys endete. Gefährliche Aktionen können seitdem mit Stud.IP-Tickets abgesichert werden. Derzeit wird #check_ticket in 14 Dateien verwendet (wie zB den Studiengruppen, der Administration von Plugins und ihren Rollen, dem Gästebuch).
+
+Augenscheinlich gibt es damit aber noch einige Stellen, die ebenfalls abgesichert werden müssten. Die derzeit verwendete Lösung müsste konsequenterweise an jeder entsprechenden Stelle auch tatsächlich eingesetzt werden. Außerdem bestehen Probleme bei der Verwendung mehrerer Tabs, da die gegenwärtige Lösung lediglich ein valides Ticket kennt.
+
+Dieser vorgeschlagene StEP möchte CSRF/XSRF verhindern, indem jeder "gefährliche" Webrequest geprüft wird. Tim Berners-Lee [Axiomen](http://www.w3.org/DesignIssues/Axioms.html) folgend, werden alle Anfragen in seiteneffektfreie und -auslösende Unterschieden. Der Aufruf des Gästebuchs sollte seiteneffektfrei sein. Das Absenden eines Eintrags an das Gästebuch löst Seiteneffekte (naiv: Datenbankänderungen) aus.
+
+Vereinfacht sollen laut Berners-Lee seiteneffektfreie Request über GET und die übrigen mit POST gesendet werden.
+
+Dieser StEP ergänzt jeden POST-Request um einen weiteren Wert, indem jedes Formular einen versteckten Parameter mitliefert. Der Server prüft beim Eintreffen eines POST-Request, ob der versteckte Parameter enthalten und valide ist. Ist das nicht der Fall, wird die Anfrage abgewiesen.
+
+Dieser versteckte Parameter ist während der Sitzung immer derselbe. Damit ergeben sich keine Probleme mit der Persistenz und Invalidierung, wie das in der derzeitigen Lösung geschieht. Die Verwendung mehrere Tabs ist daher völlig unproblematisch.
+
+Außerdem wird automatisch jeder Request aus zurkünftigem Code abgesichert, solange die Entwickler sich an die Semantik der HTTP-Verben halten (GET/POST)—was allerdings bei Formularen als gegeben angenommen werden sollte.
+
+#### Funktionsweise
+
+Um Stud.IP vor gefälschten Request zu schützen, muss nun jeder POST-Request (aber nicht Ajax) einen zusätzlichen Parameter "security_token" mitschicken, dessen Wert mit einem in der Session befindlichen verglichen wird. Um genau zu sein, wird für jeden Nutzer zu Beginn seiner Session ein 256-Bit-Token erzeugt und in der $_SESSION abgelegt. Jedes(!) Stud.IP-POST-Formular wurde um ein [=input```phptype=hidden]-Element=] bereichert, dass diesen Token mitschickt. Sobald ein Request bei Stud.IP eintrifft, wird überprüft:
+
+* ob es ein GET-Request ist, um dann die weitere Überprüfung abzubrechen
+* ob es ein XHR ist (also Ajax mit jQuery oder prototype), um dann die weitere Überprüfung abzubrechen
+* ob der mitgeschickte Parameter "security_token" existiert und mit dem Token aus der Session übereinstimmt
+
+Diese Überprüfung findet automatisch am Ende von #page_open statt, in der Annahme, dass dann die notwendige Session existiert.
+
+Fällt die Überprüfung negativ auf, wird ein Fehler (Status 403) gemeldet und die weitere Bearbeitung abgebrochen.
+
+
+#### Anwendung
+
+Zunächst ein Link zur API-Dokumentation [http://hilfe.studip.de/api/class_c_s_r_f_protection.html](http://hilfe.studip.de/api/class_c_s_r_f_protection.html)
+
+Zukünftige Entwicklungen müssen beachten, dass form-Elemente, deren "method"-Attribut den Wert POST hat, ein weiteres, verstecktes input-Element benötigen, dessen Name "security_token" und dessen Wert dem Token aus der Session entspricht. Am einfachsten macht man es so:
+
+```php
+<form method="POST" ... >
+<?= CSRFProtection::tokenTag() ?>
+...
+</form>
+```
+
+**Ganz wichtig:** Diese Methode darf '+NICHT+' aufgerufen werden, wenn es sich um ein GET-Formular handelt, da dann der Token in die URL wandert und damit über den Referer-Header an Dritt-Seiten übertragen wird. In diesem Fall wird der Schutz unwirksam.
+
+#### Schwierigkeiten
+
+Der versteckte Parameter darf niemals in eine URL gelangen, da dies alle Bemühungen über den Haufen werfen würde.
+Gerät der Token in die Hände eines Angreifers, kann dieser wieder beliebige Anfragen stellen.
+
+Die vorhandenen Tickets, die über GET-Requests versendet werden, müssen ummodelliert werden. Bisher ist dort die Umstellung noch nicht erfolgt
+
+Wenn ein seiteneffektbehafteter Request laut Stud.IP-Code eigentlich per POST verschickt werden soll, ein Angreifer aber kurzerhand einen GET-Link platziert, benötigt der Request keinen Sicherheits-Token, da automatisch lediglich bei POST-Requests überprüft wird. Daraus ergeben sich folgende Konsequenzen:
+
+Formulare **müssen** trotzdem wie oben beschrieben behandelt werden.
+Die Auswertung muss für jede Routine, die Seiteneffekte auslöst, händisch ausgewertet werden. D.h. dass also alle Stellen im Code identifiziert und um die Auswertung ergänzt werden müssen.
+Vorbereitend wurde die Klasse CSRFProtection um die Methode [#verifyUnsafeRequest](http://hilfe.studip.de/api/class_c_s_r_f_protection.html#a5b6301200e525d59e4cc63e5ea36d6d3) ergänzt. Wenn man eine Stelle im Code gefunden hat, die eigentlich per POST (genauer: alle außer GET oder HEAD) einen Seiteneffekt bewirkt, muss dort folgender Code eingefügt werden:
+
+```php
+ CSRFProtection::verifyUnsafeRequest();
+```
+
+
+ Der Aufruf überprüft, dass:
+
+* %alpha% es sich um einen unsicheren Request handelt (gemäß RFC 2616)
+* %alpha% dieser Request das Sicherheits-Token trägt
+* %alpha% das Token mit dem in der Session befindlichen übereinstimmt
+
+
+Ist das nicht der Fall gibt es eine MethodNotAllowed-Exception, falls der Request nicht unsicher ist,
+oder eine InvalidSecurityTokenException, falls das Token nicht übereinstimmt.
diff --git a/docs/docs/quickstart/dateitypen.md b/docs/docs/quickstart/dateitypen.md
new file mode 100644
index 0000000..376cc37
--- /dev/null
+++ b/docs/docs/quickstart/dateitypen.md
@@ -0,0 +1,27 @@
+---
+title: Dateitypen
+---
+
+## PHP-Dateien
+
+Die allermeisten Stud.IP-Quelldateien sind PHP-Dateien, die z.T. HTML-Teile enthalten. Mittelfristig werden PHP-Code und HTML-Ausgaben strikter getrennt - s. [HTML-Ausgaben erzeugen](html-ausgaben).
+
+## Coding-Style
+
+Alle Dateien müssen sich an die [Coding-Standards](CodingStyle) halten.
+
+## Tipps
+
+%red%TODO%%
+
+## Grafiken
+
+Grafiken werden in den Formaten JPG und PNG verwendet. Daumenregel: PNG für Bilder mit Transparenz und kleine Grafiken, die als PNG eine geringere Dateigröße haben. Für alles andere ist JPG angesagt.
+
+%red%TODO%%
+
+## Sonstige
+%red%TODO%%
+
+
+Nächste Seite [API-Dokumentation](quickstart/API-Dokumentation)
diff --git a/docs/docs/quickstart/datenbank.md b/docs/docs/quickstart/datenbank.md
new file mode 100644
index 0000000..a76285c
--- /dev/null
+++ b/docs/docs/quickstart/datenbank.md
@@ -0,0 +1,116 @@
+---
+id: datenbank
+title: Einstieg in die Datenbank von Stud.IP
+sidebar_label: Datenbank
+---
+
+
+Die Datenbank in Stud.IP umfasst etwa 100 Tabellen, wobei etliche Tabellen zum Beispiel von Plugins noch hinzukommen können. Dies hier ist ein kleiner und viel zu später Versuch, diese Tabellen zu dokumentieren.
+
+Es gibt einige Dinge, die in den Tabellen stehen, die sich niemals einem Programmierer erschließen würden, wenn er einfach nur in den Quelltext schauen würde. Es geht bei der Dokumentation nicht darum, den Blick in den PhpMyAdmin zu ersetzen. Deswegen ist die Dokumentation auch nicht vollständig und macht sich über genauste Typangaben nicht so viele Gedanken. Stattdessen sollen Felder erklärt werden wie seminare.duration_time, in denen Zahlen von -1 bis Unendlich drin stehen, die aber alle etwas anderes bedeuten können.
+
+## Grundkonventionen
+
+| Typ | Beschreibung |
+| ---- | ---- |
+| Primärschlüssel | In den Tabellen von Entitäten wie seminare oder Institute werden als Primärschlüssel sehr häufig md5-Hashes verwendet. Das hat den historischen Hintergrund, dass man IDs auf die Weise fast gar nicht erraten kann. Numerische IDs kann man sehr gut erraten. Natürlich ist dies ein minderwertiger Schutz vor Hackerangriffen auf geschützte Daten. Deswegen werden in Stud.IP die Daten mittlerweile anders geschützt. Aber wenn Primärschlüssel einmal gesetzt sind, kann man sie nur sehr schlecht wieder in numerische IDs umwandeln. Deswegen ist es stets bei den md5-Hashes geblieben mit Ausnahme von einigen neueren Tabellen. |
+| Zeitstempel | Stud.IP verwendet ausschließlich (falls es doch eine Ausnahme geben sollte, bestätigt sie die Regel) Integer-Werte als Unix-Timestamps, also die Anzahl der Sekunden seit dem 1.1.1970. Die meisten Tabellen haben zwei Felder `mkdate` und `chdate`, die man immer ausfüllen sollte. `mkdate` ist der Zeitpunkt des Erstellens des Datensatzes und `chdate` ist der Zeitpunkt des letzten Änders des Datensatzes.|
+
+## Tabellen
+
+#### auth_user_md5
+Die Tabelle auth_user_md5 ist eine der wichtigsten Tabellen für den Programmierer, weil sie für den Nutzer steht. Über diese Tabelle wird die Anmeldung geregelt und seine Email steht hier drin. Es gibt noch eine zweite Tabelle, die in einer 1-zu-1 Verknüpfung mit der auth_user_md5 steht, und zwar die user_info. Dort stehen noch einige weitere Informationen wie Hobbys und so weiter.
+
+| Tabelle | Beschreibung |
+| ---- | ---- |
+| **user_id** | Eine eindeutige Identifikationsnummer, Primärschlüssel der Tabelle. |
+| **username** | Die Anmeldekennung, soll in URLs statt der user_id verwendet werden, damit es schöner aussieht. |
+| **password** | Das mittels MD5-Hash verschlüsselte Passwort. Das bedeutet, dass selbst ein Systemadmin nicht das Passwort aus der Datenbank auslesen könnte. Bei Nutzern, die über LDAP oder irgendwie anders authentifiziert werden, ist das Passwort-Feld leer. |
+| **perms** | Globale Nutzerrechte. In Stud.IP hat ein Nutzer globale Rechte, damit zum Beispiel klar ist, dass er als 'dozent' Veranstaltungen anlegen kann. Zusätzlich gibt es auch ein lokales Rechtesystem, das hiermit aber kaum was zu tun hat. Grundsätzlich sollte jeder Nutzer von Stud.IP nirgendwo im System lokal mehr Rechte haben als global. |
+| **Vorname** | Vorname des Nutzers, als Feld groß geschrieben |
+| **Nachname** | Nachname des Nutzers, als Feld ebenfalls groß geschrieben. |
+| **Email** | Email-Adresse des Nutzers, als Feld ebenfalls groß geschrieben. Es sollte wie ein UNIQUE-Feld behandelt werden, auch wenn es das formal nicht ist. |
+| **auth_plugin** | Gibt an, ob und wie ein Nutzer authentifiziert wird. Ist das Feld leer (NULL) oder mit "standard" gefüllt, wird er über das in `password` gespeicherte Passwort in Stud.IP authentifiziert. Andere Angaben führen dazu, dass ein AuthPlugin die Authentifizierung übernimmt. |
+| **locked** | bei 1 ist der Nutzer gesperrt, kann sich also nicht mehr anmelden, bei 0 ist er nicht gesperrt. |
+
+
+
+#### Institute
+
+Achtung, diese Tabelle wird sogar im Namen groß geschrieben! Sie ist damit die einzige echte Tabelle in Stud.IP, die groß geschrieben wird.
+
+In dieser Tabelle stehen alle Informationen zu Fakultäten/Einrichtungen/Instituten/Klassen in Stud.IP.
+
+| Attribut | Beschreibung |
+| ---- | ---- |
+| **Institut_id:** | Primärschlüssel der Tabelle. Über die Institut_id (Achtung, Großschreibung beachten!) wird eine Einrichtung identifiziert. |
+
+#### seminar_cycle_dates
+
+In dieser Tabelle werden Metatermine gespeichert, was nichts anderes ist als regelmäßige Termine. Wenn ein Kurs beispielsweise einen Montagstermin hat von 10 bis 12 Uhr, dann umfasst das eine Anzahl von 12 Terminen, die in der Tabelle `termine` liegen, aber es gibt eben auch einen Meta-Termin, der alle 12 Termine auf einmal repräsentiert. Wenn man den Metatermin verändert, so werden auch alle Einträge in der Tabelle `termine` mit verändert - zumindest tun das die verantwortlichen Klassen in Stud.IP.
+
+Da es im universitären Bereich nicht nur wöchentliche Termine gibt, sondern auch zweiwöchentliche und Termine, die an jedem ersten Donnerstag eines Monats stattfinden, ist die Sache mit den Metaterminen doch etwas kompliziert geworden.
+
+| Attribut | Beschreibung |
+| ---- | ---- |
+| **weekday** | Eine Zahl von 1 bis 7, wobei 1 für Montag steht und 7 für Sonntag. |
+| **week_offset** | Anzahl der Wochen, die der Termin am Anfang des Semester noch nicht startet. Bei 0 startet der Termin also genau dann, wenn das Semester begonnen hat, bei 1 erst eine Woche später und so weiter. |
+| **cycle** | Entweder 0, 1 oder 2, wobei 0 für wöchentlich, 1 für zweiwöchentlich und 2 für dreiwöchentlich steht. |
+
+
+## Datenbankzugriff
+
+### PDO
+Stud.IP verwendet standardmäßig die MySQL-Datenbank. Um auf diese Datenbanken zuzugreifen wurde sowohl die Klasse DBManager angelegt und PDO benutzt. Alle Datenbankzugriffe in Stud.IP sollen ab jetzt direkt über PDO funktionieren. Im Quellcode bedeutet dies konkret, dass ein Datenbankzugriff so ähnlich aussieht:
+
+```php
+$db = DBManager::get();
+$result = $db->query("SELECT * FROM user_info WHERE Nachname = '".$name."'")->fetchAll();
+foreach ($result as $nutzer) {
+}
+```
+
+Der DBManager sorgt für die Verbindung zur Datenbank und PDO regelt also die Zugriffe, sobald das Verbindungsobjekt $db erst einmal initialisiert ist.
+
+Weitere Infos zu PDO findet man unter [php.net](http://de2.php.net/manual/en/class.pdo.php).
+
+Mehr Beispiele und erweiterte Funktionalität findet man hier:
+[StudipPDO](StudipPDO)
+
+### Slave-Zugriffe
+
+Wenn man lesende Zugriffe hat, deren Korrektheit nicht 100% gewährleistet sein muss (autocompleter), kann man zur Steigerung der Performance auch den Slave ansprechen:
+
+```php
+$db = DBManager::get("studip-slave");
+$result = $db->query("SELECT * FROM user_info WHERE Nachname = '".$name."'")->fetchAll();
+foreach ($result as $nutzer) {
+}
+```
+
+Wenn die Installation keine Replikation verwendet, werden die Anfragen an den Slave automatisch auf den (einzigen) Master gerichtet.
+
+# SQL-Injections
+Wichtig um SQL-Injections zu verhindern: Es gibt zwei Methoden, um eklige Datenbankhacks durch Einspeisen von bösem SQL-Code zuvor zu kommen. Das obige Beispiel ist im Grunde noch nicht geschützt.
+
+1.) Wannimmer man eine potentiell gefährliche Variable in ein SQL-Statement einfügt, sollte die Variable über $db->quote($name) geschehen. Der Quellcode dazu sieht dann so aus:
+
+```php
+$db = DBManager::get();
+$result = $db->query("SELECT * FROM user_info WHERE Nachname = '".$db->quote($name)."'")->fetchAll();
+foreach ($result as $nutzer) {
+}
+```
+
+2.) Man sollte bei einem System wie Stud.IP immer auch auf die Performance achten. Dazu kann es sehr sinnvoll sein, häufig auftauchende Befehle über prepare vorzubereiten, damit die Datenbank ein- und denselben Befehl nicht jedes mal neu umsetzen muss.
+
+```php
+$db = DBManager::get();
+$preparation = $db->prepare("SELECT * FROM auth_user_md5 WHERE Nachname = :name", array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
+$preparation->execute(array('name' => $name));
+$result = $preparation->fetchAll();
+foreach ($result as $nutzer) {
+}
+```
+
+Dies ist im Zweifelsfall die saubere Variante, weil sie der Datenbank das Leben erleichtert und zudem SQL-Injections automatisch verhindert.
diff --git a/docs/docs/quickstart/db-klassen.md b/docs/docs/quickstart/db-klassen.md
new file mode 100644
index 0000000..14c9309
--- /dev/null
+++ b/docs/docs/quickstart/db-klassen.md
@@ -0,0 +1,29 @@
+---
+id: db-klassen
+title: Dateibereichs-Klassen
+sidebar_label: Dateibereichs-Klassen
+---
+
+# Funktionsumfang
+
+Bei dem neuen Dateibereich ab der Version 4.0 gibt es viele Dinge zu beachten.
+Der Dateibereich ist zwar hauptsächlich wie zuvor eine einfache Ablage von Dateien in Stud.IP.
+Aber nebenbei gibt es jetzt auch viele Zusatzfeatures wie Hausaufgabenordner (zeitgesteuert oder nicht), Themenordner, Anbindung an Cloud-Services wie OwnCloud, Verlinkung von Dateien statt Kopien, massenweises Ersetzen mehrerer Dateien. Um diese Features zu ermöglichen, ist die Klassenstruktur etwas komplizierter als zuvor.
+
+# Datenstruktur
+
+Es gibt mehrere Entitäten, von denen die Rede ist:
+
+| Klasse | Beschreibung |
+| ---- | ---- |
+| File | Eine Datei, die im Dateisystem von Stud.IP liegt wie ein PDF-Dokument oder ein Bild. |
+| FileRef | Eine Repräsentation einer Datei. Was ein Nutzer in einem Ordner in Stud.IP sieht, ist in erster Linie immer ein FileRef-Objekt. Üblicherweise steckt hinter dem FileRef-Objekt auch ein File-Objekt, in dem die Datei tatsächlich steckt. Wenn man das Bild am Ende sieht, sieht man das File-Objekt. Aber aufgerufen hat man es, indem man auf ein FileRef-Objekt "geklickt" hat. Ein File-Objekt kann überdies auch mehreren FileRef-Objekten zugewiesen sein, wenn es im System mehrfach verlinkt ist. Das spart einerseits Speicherplatz, andererseits macht es das leichter, mehrere Dateien auf einmal zu verändern. |
+| Folder | Der Ordner in der Datenbank. Es gibt die Tabelle folders, in der gespeichert wird, welche Ordner in der Stud.IP-Datenbank liegen. |
+| Interface FolderType | Dies ist die Logik-Maschine hinter dem Ordner. Der FolderType verwaltet den Ordner und definiert, wer ihn sehen und bearbeiten kann, aber auch welche Dateien der Ordner besitzt oder momentan anzeigt. Normalerweise denken wir bei dem Begriff "Ordner" immer an den Standard-Ordner (StandardFolder), in dem wir alle Dateien sehen, die er hat, und wo alle etwas hochladen können. Aber Hausaufgabenordner (HomeworkFolder) sind spezielle Ordner, in den zwar jeder etwas hochladen kann, aber wo nur der Lehrende sehen kann, was hochgeladen worden ist. Diese Art von Logik wird immer vom FolderType verwaltet. FolderType selbst ist ein Interface und keine Klasse. Erst der HomeworkFolder ist eine echte Klasse. In der Tabelle folders wird zu jedem Ordner gespeichert, welchen folder_type er hat. Aber es gibt auch FolderTypes, die keinen passenden Eintrag in der folders-Tabelle haben wie virtuelle Ordner in der OwnCloud. Diese Ordner werden nicht in der Stud.IP-Datenbank abgespeichert, sondern nur bei Bedarf durch ein Plugin ausgelesen und als spezielle FolderTypes zurück gegeben. Das reicht, weil der Stud.IP-Code alle Aufgaben an den FolderType deligiert und möglichst nie das Folder-Objekt direkt fragt. |
+
+
+Als Entwickler kann man sich also merken: Wenn es darum geht, Klassen und Objekte zu verwenden, verwendet immer FileRef und FolderType-Klassen bzw. Objekte.
+File und Folder sollten möglichst nicht benutzt werden, auch wenn das in manchen Situationen möglich wäre.
+In anderen Situationen wie bei OwnCloud-Plugins führt sowas zu Fehlern.
+
+**Nur wer konsequent FolderType und FileRef anspricht, umgeht diese Probleme.**
diff --git a/docs/docs/quickstart/einsteiger.md b/docs/docs/quickstart/einsteiger.md
new file mode 100644
index 0000000..3e87dd0
--- /dev/null
+++ b/docs/docs/quickstart/einsteiger.md
@@ -0,0 +1,51 @@
+---
+title: Grundlagen für Einsteiger
+---
+
+### Programmieren für Stud.IP
+
+Bevor man beginnt, zur Entwicklung von Stud.IP beizutragen, sollte man wissen, was [Stud.IP CoreGroup, StEPs, TICs, Lifters und BIESTs](../rules/introduction) sind.
+
+Ebenfalls wichtig ist es, den [Coding-Stil](../coding-style) einzuhalten, sodass der geschriebene Code schneller für andere Stud.IP Entwickler verständlich ist. In dem Zusammenhang sind auch die [Namenskonventionen](../coding-style#namenskonventionen) und die Regeln für die [PHP Dateiformatierung](../coding-style#php-dateiformatierung) wichtig.
+
+Hier ein veralteter [Workshop](http://develop.studip.de/studip/download/force_download/0/8217c5e9c3b82ab83e388d8aa2ce339f/studip_programmierung_20111222.pdf) aus dem Jahr 2011 von André Noack mit einem Gesamtüberblick.
+
+### Entwicklungssystem
+
+Zum Entwickeln für Stud.IP sollte ein Computer. auf dem ein Webserver,
+mindestens PHP 7.2, eine MySQL-Datenbank, git und eine
+Entwicklungsumgebung installiert ist, verwendet werden. Wie dies alles
+eingerichtet wird, beschreibt [der Artikel zur Entwicklungsumgebung](./entwicklungsumgebung).
+
+Für die Unix-Shell gibt es auch einige [Befehle](./tipps-zum-einstieg), welche die Entwicklung in einem großen Softwaresystem wie Stud.IP erleichtern.
+
+
+### Das Stud.IP System
+
+#### Stud.IP "Seiten"
+
+In Stud.IP werden neue Seiten nicht als PHP-Skript hinzugefügt, sondern über das Framework [Trails](./trails). In diesem sind Seiten aufgeteilt in [Controller](./trails#der-controller), welche die Programmlogik enthalten und Ansichten (views), welche die Darstellung übernehmen. Sie werden durch sogenannte [Flexi-Templates](./flexi-templates) erzeugt, wobei es sich um PHP-Dateien handelt, denen Objekte und Variablen aus dem Controller übergeben werden.
+
+Um einen Controller aufrufen zu können, ist es notwendig, diese in die globale [Navigation](Navigation) einzuhängen. Das Vorgehen zum Einhängen unterscheidet sich zwischen Controllern in Plugins und Stud.IP-internen Controllern.
+
+#### Datenbank-Zugriffe
+
+Intern verwendet Stud.IP PDO, um auf Datenbanktabellen zuzugreifen. Diese Zugriffe sind über [SORM (SimpleORMap)](./simpleormap) abstrahiert. Dabei handelt es sich um ein kleines Framework, welches die Überführung von Datenbankeinträgen in Objekte übernimmt.
+
+#### Übersetzung
+
+Stud.IP ist in zwei Sprachen erhältlich: Deutsch und Englisch. Im Quellcode werden deutsche Zeichenketten verwendet, welche mittels der PHP-Funktion `gettext` ins Englische übersetzt werden. Für PHP- und JavaScript-Code ist die Vorgehensweise unterschiedlich:
+
+* [in PHP](Howto/Internationalisierung#internationalisierung-im-php-code)
+* [in JavaScript](Howto/Internationalisierung#internationalisierung-im-js-code)
+
+
+#### Plugins
+
+Plugins sind ein bedeutender Bestandteil von Stud.IP. Mit diesen kann die Funktionsweise von Stud.IP verändert werden oder es können neue Funktionen zu Stud.IP hinzugefügt werden.
+
+Die folgenden Seiten geben eine Einführung in die Plugin-Entwicklung:
+
+* ['Erstellung eines Plugins' von Moritz Strohm (PDF)](https://develop.studip.de/studip/dispatch.php/document/download/5747961f81b385b1520cf7dc393f1db6)
+* ['Plugin-Tutorial' von Elmar Ludwig](PluginTutorial)
+
diff --git a/docs/docs/quickstart/entwicklungsumgebung.md b/docs/docs/quickstart/entwicklungsumgebung.md
new file mode 100644
index 0000000..45709cf
--- /dev/null
+++ b/docs/docs/quickstart/entwicklungsumgebung.md
@@ -0,0 +1,236 @@
+---
+title: Entwicklungsumgebung
+---
+
+Um selbst mitzuentwickeln, brauchst du ein lokales Testsystem auf einem Rechner, für den du alle nötigen Rechte hast.
+
+Das heißt im Einzelnen, dass du folgendes brauchst
+
+* Ein Webserver (Apache oder Nginx), vorzugsweise mit Schreibzugriff auf die zentralen Konfigurationsdateien
+* PHP ab Version 7
+* Schreibrechte im Dateisystem sowohl für Dateien, die für dne Webserver erreichbar sind, als auch für solche, die außerhalb liegen
+* Voller Zugriff auf eine MySQL-Datenbank MySQL oder MariaDB)
+* Einen Git-Client zum Auschecken der aktuellsten Entwickler-Version von Stud.IP
+* Ein Editor oder eine Entwicklungsumgebung (bspw. PHPStorm) zum Bearbeiten von Dateien
+
+## Serverumgebung
+
+Um für Stud,IP zu entwickeln brauchst du Zugriff auf einen Webserver und dessen Dateisystem.
+Den kannst du auf deinem eigenen Rechner einrichten oder einen vorhanden Server nutzen, auf den du per SSH oder anderem Remote-Zugriff zugreifst.
+Im Folgenden werden einige erprobte Lösungen für verschiedene Server-Betriebssysteme aufgeführt.
+
+### Linux
+
+Du benutzt Linux? Dann könnte man ja fast davon ausgehen, dass Du weißt, wie man einen Webserver installiert. Aber beispielhaft für ein Ubuntu Linux müsstest Du folgende Schritte durchgehen:
+
+- Apache installieren (alternativ nginx): `sudo apt install apache2`
+- MariaDB installieren: `sudo apt install mariadb`
+- PHP installieren: `sudo apt install php libapache2-mod-php php-mysql`
+- git installieren: `sudo apt install git`
+- Stud.IP-Repository auschecken: `git clone git@gitlab.studip.de:studip/studip.git`
+- In dem Stud.IP-Verzeichnis `make` ausführen.
+- Im Ordner `./config` die Datei `config.inc.php.dist` nach `config.inc.php` kopieren.
+- Im Ordner `./config` die Datei `config_local.inc.php.dist` nach `config_local.inc.php` kopieren und dann diese Datei bearbeiten. Hier müssen mindestens die Variablen `$DB_STUDIP_USER` und `$DB_STUDIP_PASSWORD` so gesetzt werden, dass dort die Zugangsdaten zur MariaDB bzw. MySQL drin stehen.
+- Die Datei `./config/.htaccess.dist` nach `./public/.htaccess` kopieren (oder alternativ die Apache-Konfiguration oder die php.ini verändern).
+- In MariaDB bzw. MySQL eine neue Datenbank `studip` anlegen: `CREATE DATABASE studip CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`
+- Und dann aus dem Ordner `./db` nacheinander in dieser Reihenfolge die Dateien `studip.sql`, `studip_root_user.sql`, `studip_default_data.sql`, `studip_demo_data.sql`, `studip_mvv_demo_data.sql`, `studip_resources_default_data.sql` und `studip_resources_demo_data.sql` einspielen.
+- Wenn jetzt noch der `DocumentRoot` des Apache auf den Ordner `public` von Stud.IP zeigt, sollte alles laufen.
+
+### Windows
+
+Diese Installationsanleitung erläutert die Installation eines Stud.IP Testsystems
+in der Version 4.6 auf Windows. Eine Anleitung für Unix-Betriebssysteme findet sich
+[hier](https://hilfe.studip.de/admin/Admins/Installationsanleitung).
+
+Für die Installation werden folgende Anwendungen benötigt
+- Apache Webserver
+- MySQL/MariaDB Datenbankserver: Mindestens MySQL 5.7 oder MariaDB-10.2.3
+- PHP: mindestens PHP 7.2, höchstens PHP 8.1
+
+In dieser Anleitung wird demonstrativ die Apache-Distribution XAMPP in der Version 7.3.28 verwendet.
+Bei der Wahl einer anderen Version sollte auf eine kompatible PHP Version geachtet werden.
+Es wird außerdem nur eine Basisinstallation erstellt,
+also die möglichst einfachste Variante, ein Testsystem in Betrieb zu nehmen,
+ohne zu sehr ins Detail der einzelnen Aspekte zu gehen.
+Es geht also eher darum, einfach mal ein Testsystem aufzusetzen,
+welches beispielsweise zum Entwickeln von Plugins genutzt werden kann.
+Für weitere und ausführliche Erläuterungen sollte die oben verlinkte Unix-Installationsanleitung betrachtet werden.
+
+Die Stud.IP Dateien können mittels git unter folgender Repository Adresse geladen werden:
+`git clone git@gitlab.studip.de:studip/studip.git`
+
+
+## Apache Konfiguration
+
+Der "document-root" von dem XAMPP die anzuzeigenden PHP-Dateien lädt,
+befindet sich standardmäßig unter `C:/xampp2/htdocs`.
+Dies heißt, dass alle anzuzeigenden Dateien (also beispielsweise das Stud.IP Verzeichnis) in diesem liegen müssen,
+damit sie über `localhost` geöffnet werden könne.
+Alternativ kann der document-root von Apache auch beliebig angepasst werden.
+Dazu muss in der Datei http.conf (xampp-verzeichnis\apache\conf\http.conf)
+die Einstellung `DocumentRoot` und `Directory` (~ Zeile 252f) auf das neue Verzeichnis umgestellt werden.
+Bedacht werden sollte, dass bei jeder Änderung von config Dateien (http.conf, php.ini etc.)
+die betroffenen Anwendungen (Apache, MySQL etc.) neu gestartet werden müssen, damit die Änderungen übernommen werden.
+Um den document-root im Verzeichnis `C:\Users\MaxMustermann\PhpstormProjects` zu setzen,
+könnte die http.conf an betreffender Stelle beispielsweise so aussehen:
+
+```
+\[...\]
+DocumentRoot "C:\Users\MaxMustermann\PhpstormProjects"
+<Directory "C:\Users\MaxMustermann\PhpstormProjects">
+\#
+\[...\]
+```
+
+## PHP Konfiguration
+
+In der php.ini (xampp-verzeichnis\php\php.ini) müssen folgende Konfigurationen getroffen werden.
+Hinter der jeweiligen Einstellung ist die Zeile angegeben,
+falls XAMPP in der gleichen Version neu installiert wurde,
+andernfalls kann in der Regel mit Strg+F die ini Datei einfach durchsucht werden.
+Außerdem sollte angemerkt werden, dass Zeilen, die mit einem semicolon (`;`) beginnen, auskommentiert sind.
+Also sollte entweder ein anderer Eintrag zur Überschreibung der Einstellung gesucht werden (bspw. `error_reporting` ),
+oder falls nur dieser Eintrag existiert, das semicolon entfernt werden (bspw. `mbstring.internal_encoding`).
+
+- short_open_tag = On (Zeile 192)
+- max_execution_time = 300 (Zeile: 380)
+- max_input_vars = 10000 (Zeile: 397)
+- memory_limit = 1024M (Zeile: 401)
+- error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT  & ~E_NOTICE (Zeile: 457)
+- post_max_size = 514M (Zeile: 687)
+- default_charset  = "UTF-8" (Zeile: 706)
+- upload_max_filesize = 512M (Zeile: 839)
+- allow_url_fopen = On (Zeile: 850)
+- mbstring.internal_encoding  = "UTF-8" (Zeile: 1670)
+
+## Stud.IP Konfiguration
+
+### Basis Konfiguration
+
+Wenn der Webserver und PHP richtig konfiguriert wurden, kann Stud.IP konfiguriert werden.
+Dafür existiert seit der Stud.IP Version 4.5 ein Installationsassistent der automatisch beim ersten Aufruf der Startseite aufgerufen wird.
+Stud.IP ist nach der Konfiguration in einem Webbrowser unter `localhost` (oder der IP-Adresse des Rechners auf dem XAMPP läuft) verfügbar.
+Um die Startseite zu öffnen, muss in den Ordner `public` navigiert werden.
+Beispiel: Wenn das komplette Stud.IP Verzeichnis im `DocumentRoot` liegt und `4.6` heißt,
+kann mit der URL `localhost/4.6/public` im Webbrowser die Startseite aufgerufen werden.
+Falls Stud.IP noch nicht konfiguriert wurden, wird der Installationsassistent geöffnet.
+
+Im Folgenden werden alle Schritte des Installationsassistenten durchgegangen, essenzielle Schritte sind fett markiert.
+
+Schritt 1:
+Assistent starten klicken
+**Schritt 2**:
+Hier wird angezeigt, ob PHP richtig konfiguriert wurde. Falls Teile nicht richtig konfiguriert wurden, wird hier darauf hingewiesen.
+Falls die richtige XAMPP version installiert wurde und die PHP Konfiguration richtig stattgefunden hat,
+sollten hier alle Einstellungen "ok" sein. Falls doch noch Änderungen gemacht werden, sollte dran gedacht werden,
+den Apache Server neu zu starten.
+**Schritt 3**:
+Hier wird die Datenbank initial erstellt, als Host wird `localhost` und als Nutzer `root` empfohlen.
+Wenn der MySQL/MariaDB Server gestartet ist (in XAMPP das Modul unter Apache),
+sollte das Prüfen der Verbindung erfolgreich sein.
+Schritt 4:
+Hier wird angezeigt, ob MariaDB richtig konfiguriert ist.
+Mit der empfohlenen XAMPP Version sollte bereits, ohne weitere Einstellungen getroffen zu haben, alles stimmen.
+Schritt 5:
+In diesem Schritt wird geprüft, ob ausgewählte Datenverzeichnisse beschreibbar sind.
+Unter Windows sollten standardweise keine weiteren Einstellungen nötig sein und die Verzeichnisse beschreibbar sein.
+Schritt 6:
+Hier können die Standard-Daten für die Datenbank erstellt werden. Die Installation optionaler Demo Daten wird hier empfohlen,
+falls nicht anderweitig Daten verfügbar sind.
+Die restlichen, zu treffenden Einstellungen sind lediglich für Produktivsysteme relevant und können daher für dieses Testsystem beliebig gefüllt werden.
+**Schritt 7**:
+Hier ist es möglich ein root Konto für den Login in das Stud.IP System zu erstellen.
+Dieses root-Konto sollte sich gemerkt werden, da nur root-Nutzer Zugang zu Funktionen wie dem Installieren von Plugins besitzen.
+Schritt 8:
+Hier muss lediglich gewartet werden, dass die Installation durchgeführt wird.
+Schritt 9:
+Bestätigung, dass die Installation (hoffentlich) erfolgreich war.
+
+### Weitere Konfiguration
+Unter Windows müssen und können noch weitere Konfigurationsschritte durchgeführt werden.
+Alle diese sollten in die lokale Konfigurationsdatei `config_local.inc.php` (`<studip-verzeichnis>/config/config.local.inc.php`)
+innerhalb des zweiten `namespace` getroffen werden. Hier findet sich auch die Einstellung für die Verbindung mit der Datenbank,
+falls gewünscht wird, diese anzupassen.
+Eine config.local.inc.php Datei, die alle folgenden Einstellungen getroffen hat, ist hier sonst separat verfügbar. Eventuell muss die Datenbankkonfiguration innerhalb dieser jedoch angepasst werden.
+[config_local.inc.php](../assets/3310a5850c6c4ed2d0b55a1884e5a39b/config_local.inc.php)
+
+Es wird ein `tmp` Verzeichnis/Ordner zum Ablegen von temporären Dateien benötigt.
+Empfohlen wird hier das Verzeichnis im Stud.IP Verzeichnis zu erstellen (auf derselben Ebene wie die Verzeichnisse `app`, `lib`, `config`, `public` etc.).
+In der oben genannten Konfigurationsdatei muss der Variable `$TMP_PATH` der Pfad des tmp-Verzeichnisses zugewiesen werden.
+Falls das `tmp`-Verzeichnis im Stud.IP Verzeichnis angelegt wurden, kann einfach die folgende Zeile in die Konfigurationsdatei übernommen werden.
+`$TMP_PATH = $STUDIP_BASE_PATH . "/tmp";`
+
+Um mögliche Fehlermeldungen zu verhindern, sollte das Caching abgeschaltet werden.
+Dazu kann die folgende Zeile der Konfigurationsdatei hinzugefügt werden.
+`$CACHING_ENABLE = false;`
+
+Da es sich hierbei um ein Testsystem handelt, wird vermutlich kein Mailserver benötigt, um E-Mails abzuschicken,
+jedoch sollte dann, um Fehlermeldungen zu vermeiden, das Verschicken von Mails unterbunden werden.
+Dazu kann die folgende Zeile der Konfigurationsdatei hinzugefügt werden.
+`$MAIL_TRANSPORT = 'debug';`
+
+In einigen Stud.IP Versionen ist außerdem die Aufnahme von folgender Zeile in die Konfigurationsdatei zu empfehlen,
+um PHP Warnung zu verhindern:
+`define("LC_MESSAGES", 5);`
+
+Das Stud.IP an der Universität Oldenburg bietet die Möglichkeit Texte auf Englisch anzuzeigen.
+Um dies auch in der Entwicklung zu berücksichtigen, sollte Englisch als Sprache hinzugefügt werden.
+Dazu kann die folgende Zeile der Konfigurationsdatei hinzugefügt werden.
+`$CONTENT_LANGUAGES['en_GB'] = array('picture' => 'lang_en.gif', 'name' => 'English');`
+
+Falls alle nötigen und gewünschten Einstellungen getroffen worden sind, sollte die Stud.IP Testumgebung nun verwendbar sein.
+
+
+### Mac OS
+
+// TODO
+
+## Aktuelle Version besorgen
+
+Die Stud.IP-Entwickler verwenden SVN für Versionsverwaltung, alle offiziellen Versionen sind anonym und öffentlich lesbar.
+Einen einfachen Einblick bietet die Gitlab unter https://gitlab.studip.de
+
+**Wichtig:**
+
+Es gibt immer genau EINE Stud.IP-Version, an der aktiv entwickelt wird.
+Die liegt im Git-Repository unter [main](https://gitlab.studip.de/studip/studip/-/tree/main).
+Alle 6 Monate wird aus dem dann aktuellen Repository ein Release geschnürt. A
+lte Releases werden z.T. noch mit Bugfixes versorgt, ansonsten arbeiten alle Entwickler immer im main.
+
+*Lesen* darf jeder: Mit deinem Git-Client kannst du unter https://gitlab.studip.de/studip/studip.git den kompletten Code auschecken.
+Es gibt eine ganze Reiher verschiedener Branches, die zum Teil sehr speziell sind. Du brauchst vor allem folgende Infos:
+
+
+git-Kommando zum Auschecken der aktuellen Entwickler-Version:
+```shell
+git clone https://gitlab.studip.de/studip/studip.git
+```
+
+*Schreiben* darf allerdings nicht jeder.
+Zwar freuen wir uns über jeden, der eigene Entwicklungen, Bugfixes und Verbesserungen zu Stud.IP beitragen möchte, aber ohne sorgfältige Qualitätssicherung sollte natürlich kein Code in die aktuelle Version gelangen.
+Deshalb dürfen nur ausgewählte Entwickler Code in das Repository einchecken.
+Solange du noch nicht dazu gehörst, führt dein Weg über das Developer-Board.
+
+## Installation und Konfiguration
+
+Alle wichtigen Hinweise zur Stud.IP-Installation sind in der [Admins/Installationsanleitung](Admins/Installationsanleitung) aufgeführt.
+Da du keine Release-Version installierst, sondern eine SVN-Version, musst du folgende Unterschiede beachten:
+
+
+## Entwicklungsumgebung
+
+### Einfacher Texteditor
+
+Minimalanforderung ist ein einfacher Texteditor, z.B. vi oder nano.
+
+### Erweiterter Texteditor
+
+Erweiterte Texteditoren wie z.B. Kate bieten wesentlich mehr Komfort als vi oder nano, da sie Syntaxhervorhebung, geteilte Ansichten (mehrere Dateien in einem Fenster) und automatische Texteinfügung besitzen.
+
+### IDE (PHPSTORM)
+// TODO
+
+## Änderungen einchecken
+
+*Schreiben* darf wie gesagt nicht jeder im SVN. Zwar freuen wir uns über jeden, der eigene Entwicklungen,
+Bugfixes und Verbesserungen zu Stud.IP beitragen möchte, aber ohne sorgfältige Qualitätssicherung sollte natürlich kein Code in die aktuelle Version gelangen. Deshalb dürfen nur ausgewählte Entwickler Code in das Repository einchecken. Solange du noch nicht dazu gehörst, führt dein Weg über das Developer-Board.
diff --git a/docs/docs/quickstart/helpbar.md b/docs/docs/quickstart/helpbar.md
new file mode 100644
index 0000000..6072fde
--- /dev/null
+++ b/docs/docs/quickstart/helpbar.md
@@ -0,0 +1,33 @@
+---
+id: helpbar
+title: Einbindung von Hilfe-Inhalten
+sidebar_label: Hilfe-Inhalten
+---
+
+
+In der Helpbar werden die Hilfe-Inhalte angezeigt. Dazu gehören:
+
+*der Link zum Hilfe-Wiki
+*Kurze Hilfetexte, die in früheren Versionen in der Infobox angezeigt wurden
+*Touren
+
+## Hilfe-Texte
+Die Hilfe-Texte werden aus der Datenbank geladen und sind stets für eine Route gültig.
+
+Es ist möglich, für die Anzeige das Vorhandensein von Request-Parametern zur Bedingung zu machen, z.B.: "wiki.php?view=edit".
+
+## Hilfe-Touren
+Auch Touren können für eine Route definiert werden. Eine Tour besteht aus einzelnen Schritten, die (wie Tooltips) auf Elemente einer Seite bezogen sind.
+
+Es gibt zwei verschiedene Arten von Touren (tour und wizard): die modale *tour* erlaubt keine Eingaben, beim *wizard* bleibt Stud.IP aktiv bedienbar. Es ist möglich, Touren so zu konfigurieren, dass sie automatisch beim (ersten) Aufruf der Seite gestartet werden.
+
+Zum Wechseln zwischen Tour-Schritten gibt es eine Kontrollleiste mit fest platzierten Bedienelementen (Weiter, Zurück, Beenden).
+Touren können über mehrere Stud.IP-Seiten führen.
+
+## Hilfelasche für Plugins
+
+Derzeit ruft man dafür einfach `Helpbar::get()->addPlainText` auf. Siehe [Beispiel](https://gist.github.com/luniki/2ca7d97317c697702795)
+
+## Hilfelasche ausblenden
+
+Um die Hilfelasche auszublenden, ruft man `Helpbar::get()->shouldrender(false);` auf
diff --git a/docs/docs/quickstart/html-ausgaben.md b/docs/docs/quickstart/html-ausgaben.md
new file mode 100644
index 0000000..0c20156
--- /dev/null
+++ b/docs/docs/quickstart/html-ausgaben.md
@@ -0,0 +1,87 @@
+---
+title: HTML-Ausgaben
+---
+
+## Flexi-Templates
+Stud.IP verwendet zur Ausgabe von HTML Templates, genauer gesagt eine Eigenentwicklung namens [Flexi-Templates](./flexi-templates)
+
+Der einzige Unterschied ist, dass es in Stud.IP schon eine TemplateFactory instanziiert ist, die man einfach verwenden kann.
+
+```php
+$template = $GLOBALS['template_factory']->open('shared/searchbox.php');
+echo $template->render();
+```
+
+## Templates und Klassen für Alle(s)
+
+#### Meldungen
+
+Um in Stud.IP Meldungen anzuzeigen, verwendet man die Klasse [MessageBox](./message-box).
+
+Hier ein einfaches Beispiel:
+```php
+// Beispiel für eine einfache Info-Nachricht
+echo MessageBox::info('Nachricht');
+```
+
+Möchte man die Meldung nicht sofort, sondern erst zusammen mit dem Seiten-Layout ausgeben, sollte man die Ausgabe an die [PageLayout](./page-layout)-Klasse delegieren:
+```php
+// Beispiel für eine einfache Info-Nachricht
+$info = MessageBox::info('Nachricht');
+PageLayout::postMessage($info);
+```
+
+Alle Details und weitere Typen von MessageBoxen findet man in der [Dokumentation](./message-box).
+
+#### Suchbox
+
+Das Template `searchbox` bietet eine einheitliche Suchmaske für alle Seiten, auf denen gesucht werden soll. Das Template ist recht minimalistisch in kann in eine HTML-Form gebettet werden.
+
+Verwendung im Template
+```php
+<form action="<?=URLHelper::getLink()?>" method=post>
+ <?= $this->render_partial('shared/searchbox'); ?>
+</form>
+<?
+$searchterm = Request::get('searchterm');
+```
+
+#### Paginierung
+
+Das Template `pagechooser` ist für Seiten, die eine Paginierung haben sollen. Man gibt dem Template die Anzahl an Elementen, Elemente pro Seite, die aktuelle ausgewählte und einen Link mit Formatauszeichnung wo die Seitenzahl sein soll mit, dann erhält man oben gezeigten Seitenwähler.
+
+Seit Stud.IP 2.1 befindet sich ein globaler Wert in der Datenbank. Dieser ist mit get_config('ENTRIES_PER_PAGE') nutzbar. Der Standard-Wert ist 20.
+
+```php
+<?= $this->render_partial("shared/pagechooser",
+ array(
+ "perPage" => get_config('ENTRIES_PER_PAGE'),
+ "num_postings" => $numberOfPersons,
+ "page"=>$page,
+ "pagelink" => "score.php?page=%s"));
+?>
+```
+
+#### Modaler Dialog
+
+Manchmal ist es notwendig bei sehr wichtigen Rückfragen einen [modalen Dialog](./modaler-dialog) statt einer normalen MessageBox zu verwenden.
+
+Beispiel:
+```php
+$question = sprintf(_('Möchten Sie wirklich den User **%s** löschen ?'), $username);
+echo createQuestion( $question,
+ array(
+ "studipticket" => get_ticket(),
+ 'u_kill_id' => $u_id
+ ),
+ array(
+ 'details' => $username
+ )
+);
+```
+
+Ab Version 4.2 kann stattdessen PageLayout::postQuestion verwendet werden.
+
+```php
+PageLayout::postQuestion($question, $accept_url = *, $decline_url = *)
+```
diff --git a/docs/docs/quickstart/internationalisierung.md b/docs/docs/quickstart/internationalisierung.md
new file mode 100644
index 0000000..7463935
--- /dev/null
+++ b/docs/docs/quickstart/internationalisierung.md
@@ -0,0 +1,222 @@
+---
+title: Lokalisierung (L10n)
+---
+
+Lokalisierung steht in der Softwareentwicklung für die Anpassung von Inhalten (Bücher, Filmkunst, Homepages), Prozessen, Produkten und insbesondere Computerprogrammen (Software) an die in einem bestimmten geographisch oder ethnisch umschriebenen Absatz- oder Nutzungsgebiet (Land, Region oder ethnische Gruppe) vorherrschenden lokalen sprachlichen und kulturellen Gegebenheiten.
+
+Das englische Wort für Lokalisierung ist localization (amerikanisches/britisches Englisch) bzw. localisation (britisches Englisch) und wird in der Softwareentwicklung oft mit L10N abgekürzt. Die 10 ist die Anzahl der ausgelassenen Buchstaben. (Im Gegensatz dazu steht I18N für internationalization.)
+
+> Wikipedia, L10N http://de.wikipedia.org/wiki/L10N
+
+Lokalisierung wird an verschiedenen Stellen wichtig:
+Die Übersetzungen der Texte im Code werden in unregelmässigen Abständen automatisch extrahiert
+und auf der Plattform [Transifex](https://www.transifex.com/projects/p/studip/) gemeinschaftlich übersetzt.
+
+
+# Internationalisierung im PHP-Code
+
+Stud.IP nutzt für die Internationalisierung das auch in vielen anderen Software-Projekten verwendete gettext-Paket.
+
+Dabei erfolgt eine Trennung zwischen der Vorbereitung der lokalisierten Ausgabe von Texten (Internationalisierung, dies ist Aufgabe jedes Programmierers) und der eigentlichen Übersetzung der Texte mit Hilfe spezieller Tools wie z.B. "kbabel" (Lokalisierung, dies ist die Aufgabe des Maintainers einer Sprachdatei).
+
+Die Ausgangssprache, die im Quellcode verwendet wird, ist bei Stud.IP deutsch.
+
+Alle Strings im System dürfen nicht in den HTML-Teilen der Sourcedateien stehen, sondern müssen aus PHP-Abschnitten heraus geschrieben werden.
+Die zu übersetzenden Zeichenfolgen werden im Programmcode in die spezielle Funktion `gettext()` eingeschlossen. Benutzt werden sollte nur die Kurzform, in PHP realisiert als `_()`.
+
+```php
+echo _("Meine Veranstaltungen")
+```
+
+In die zu übersetzenden Strings sollte reiner Text, keine HTML-Struktur der Seite und kein Programmcode wie z.B. Variablennamen eingeschlossen werden.
+
+Falsch:
+```php
+echo _("<tr><td>Meine Veranstaltungen</td></tr>");
+```
+Richtig:
+
+```php
+echo "<tr><td>" . _("Meine Veranstaltungen") . "</td></tr>";
+```
+
+Oder Richtig:
+
+```php
+printf("<tr><td>%s</td></tr>", _(" Meine Veranstaltungen "));
+```
+
+Falsch:
+
+```php
+print _("error§Keine Berechtigung!§");
+```
+
+Richtig:
+
+```php
+printf("error§%s§", _("Keine Berechtigung!"));
+```
+
+Falsch:
+```php
+echo _("Sie haben $count neue Nachrichten.");
+```
+Auch falsch:
+```php
+echo _("Sie haben ") . $count . _(" neue Nachrichten.");
+```
+Richtig:
+```php
+printf(_("Sie haben %s neue Nachrichten."), $count);
+```
+
+Die in einen `gettext()` eingeschlossenen Strings sollten vollständige Sätze bzw. Informationsblöcke enthalten, also kein Zusammenstückeln aus einzelnen Teilstrings (siehe oben).
+
+Schliessen sich die beiden vorangegangenen Vorschriften gegenseitig aus, weil z.B. ein Teil eines Satzes formatiert wird, so hat die letztere Regel Vorrang (der Übersetzer braucht sowieso html-Grundlagenkenntnisse).
+
+Falsch:
+```php
+echo _("Sie können diese Datei ") . "<b>" . _("nicht") . "</b>" . _(" löschen");
+```
+Richtig:
+```php
+echo _("Sie können diese Datei <b>nicht</b> löschen");
+```
+
+Komplizierte html-Ausdrücke, wie z.B. ein klickbares Icon im Text sollten dagegen via `%s` aus dem String herausgezogen werden
+
+Richtig:
+```php
+printf(_("Unter %s gelangen Sie zu Ihren Terminen."), "<a href><img src = \"pictures/icon-lit.gif\"></a>");
+```
+
+## Text-Buttons
+
+Beschriftete und damit zu übersetzende Formular-Buttons werden generell nicht direkt in den Code eingebunden,
+sondern immer über die [Button-Api](Buttons) erzeugt, diese kümmert sich dann um die Lokalisierung.
+
+
+# Internationalisierung im JS-Code
+
+
+Um in den Genuss der vorhandenen Gettext-Übersetzungen auch in JavaScript-Code zu kommen, verwenden wir in Stud.IP einen speziellen Web-Service, der ausgesuchte Übersetzungen in JavaScript-Code umwandelt und diese für die von [Eli Grey geschriebene l10n.js-Bibliothek](http://purl.eligrey.com/l10n.js) verfügbar macht.
+
+## Web-Service
+
+Der Web-Service findet sich in jeder Stud.IP-Installation ab Version 2.0 unter der URL: `dispatch.php/localizations/{locale}`
+
+Auf dem offiziellen Entwicklungsserver der Stud.IP Core Group können daher die deutschen Übersetzungen unter:
+
+`http://develop.studip.de/studip/dispatch.php/localizations/de_DE`
+
+und die englischen unter:
+
+`http://develop.studip.de/studip/dispatch.php/localizations/en_GB`
+
+erreicht werden.
+
+Sollte man ein nicht verfügbares Länderkürzel angeben, liefert der Web-Service den Status-Code 406 (Not acceptable) und eine JSON-Liste mit den tatsächlich verfügbaren locales.
+
+Dieser Web-Service wird von den folgenden Dateien (und damit auf nahezu jeder Seite) automatisch eingebunden, wobei das jeweils aktivierte locale verwendet wird:
+
+* `lib/include/html_head.inc.php`
+* `templates/layouts/base.php`
+* `templates/layouts/base_without_infobox.php`
+
+Diese Seiten binden auch automatisch die oben erwähnte JavaScript-Bibliothek [l10n.js](http://purl.eligrey.com/l10n.js) ein.
+
+### JavaScript-API
+
+Die offizielle JavaScript-Client-API enthält die Methode [`Object#toLocaleString`](https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Object/toLocaleString), die wie folgt definiert ist:
+
+> Returns a string representing the object. This method is meant to be overriden by derived objects for locale-specific purposes.
+
+Für Strings ruft diese Methode `String#toString` auf. An dieser Stelle setzt die Bibliothek an und definiert die vorhandene Methode um.
+
+Wenn man jetzt also einen String in JavaScript übersetzen möchte, ruft man lediglich `toLocaleString` auf.
+
+
+Beispiel:
+
+```javascript
+var aString = "suchen".toLocaleString();
+
+// ergibt bei aktiviertem locale de_DE:
+// aString === "suchen"
+
+// ist hingegen en_GB aktiv:
+// aString === "search"
+```
+
+Es werden lediglich die Strings übersetzt, die in der Liste des Web-Services enthalten sind. Nicht enthaltene bleiben, wie sie sind.
+
+
+## Neue Strings aufnehmen
+
+Neue Strings können einfach über die oben genannte `String.toLocaleString()`-Methode im JavaScript ausgezeichnet werden.
+Anschliessend sollte das CLI-Skript `extract-js-localizations.php` angestossen werden, welche diese Strings extrahier und in die Datei [`app/views/localizations/show.php`](https://develop.studip.de/trac/browser/trunk/app/views/localizations/show.php) schreibt. Dadurch steht der Übersetzungsmechanismus auch für diese Strings bereit.
+
+
+# I18N
+
+Seit Stud.IP 3.5 gibt es die Möglichkeit, Datenbankinhalte internationalisiert abzuspeichern. Die entsprechende Funktionalität kann mit SimpleORMap-Klassen einfach verwendet werden, ohne dass viel Code geschrieben werden muss. Im Folgenden wird anhand eines Beispieles gezeigt, wie bestimmte Felder einer SimpleORMap-Klasse internationalisiert werden können.
+
+### Beispiel zur Internationalisierung
+
+Im Folgenden wird die SimpleORMap-Klasse ResourceProperty um zwei internationalisierte Felder erweitert.
+ResourceProperty speichert Ressourceneigenschaften und besitzt die Felder "description" für eine Beschreibung der Eigenschaft und "display_name", um den anzuzeigenden Namen der Eigenschaft zu definieren.
+
+#### Anpassung der SimpleORMap-Klasse
+
+In der statischen configure-Methode werden zum assoziativen Array $config die folgenden Einträge hinzugefügt:
+
+```php
+$config['i18n_fields']['display_name'] = true;
+$config['i18n_fields']['description'] = true;
+```
+
+Damit wurde die SimpleORMap-Klasse bereits auf internationalisierte Datenfelder angepasst.
+
+#### Benutzung im View
+
+Internationale Datenfelder werden angezeigt, indem statische Methoden der I18N-Klasse aufgerufen werden, welche die passenden HTML-Eingabefelder erzeugen.
+Für das Feld "description" ist eine Textarea notwendig, für "display_name" hingegen ein einfaches Eingabefeld.
+Im View wird zur Anzeige der Felder folgender Code eingefügt:
+
+```php
+<label>
+<?= _('Beschreibung')?>
+<?= I18N::textarea('description', $property->description) ?>
+</label>
+<label>
+<?= _('Angezeigter Name') ?>
+<?= I18N::input('display_name', $property->display_name) ?>
+</label>
+```
+
+Damit werden die internationalisierten Datenfelder in den passenden Eingabefeldern angezeigt.
+Die Methoden `I18N::textarea()` und `I18N::input()` nehmen als ersten Parameter den Namen des Feldes im HTML-Formular, der als name-Attribut zu den Eingabefeldern hinzugefügt wird und als zweiten Parameter den Wert des Feldes.
+
+#### Verarbeiten von Eingaben
+
+Wurde ein HTML-Formular abgesendet, in dem internationalisierte Datenfelder vorhanden sind, so werden die Inhalte der Datenfelder
+mit der Methode `Request::i18n()` statt `Request::get()` ausgelesen. Die Inhalte können dann direkt den Datenbankfeldern der SimpleORMap-Klasse zugewiesen werden.
+Im Falle der als Beispiel genutzten ResourceProperty-Klasse sieht dies so aus:
+
+```php
+//Einlesen der Datenfelder aus dem Request:
+$description = Request::i18n('description');
+$display_name = Request::i18n('display_name');
+
+//Zuweisen zum ResourceProperty-Objekt:
+$property->description = $description;
+$property->display_name = $display_name;
+
+//Speichern:
+if ($property->isDirty()) {
+$property->store();
+}
+```
+
+
diff --git a/docs/docs/quickstart/javascript.md b/docs/docs/quickstart/javascript.md
new file mode 100644
index 0000000..7c3b3d0
--- /dev/null
+++ b/docs/docs/quickstart/javascript.md
@@ -0,0 +1,190 @@
+---
+title: JavaScript
+---
+
+In Stud.IP wird mit der Zeit mehr und mehr JavaScript für erweiterte und vereinfachte Bedienung oder auch einfach für besonders schöne Effekte benutzt.
+
+## Code-Konventionen für JavaScript
+
+Als Programmierer hat man sich im Grunde nur daran zu richten, dass der globale Namespace einigermaßen sauber gehalten wird (also globale Variablen müssen vermieden werden) und dass die Code-Konventionen eingehalten werden, die im Lifters005 zusammen gefasst sind. Die Code-Konventionen sind von [Douglas Crockfords "Code Conventions for the JavaScript Programming Language"](http://javascript.crockford.com/code.html) komplett übernommen.
+
+Was den Namespace angeht, so sollen alle speziellen Stud.IP Funktionen unterhalb des STUDIP-Objektes angehängt werden. Programmiere ich also einige Funktionen für die News, fange ich an mit:
+
+```js
+STUDIP.News = {
+ openclose: function (id) {},
+ open: function (id) {},
+ close: function (id) {}
+}
+```
+
+und fülle diese Methoden dann mit Leben. An diese Konvention sollten sich auch Plugin-Programmierer halten, wobei die noch speziell darauf zu achten haben, dass ihre Methodennamen auch tatsächlich eindeutig sind. Implementieren zwei unterschiedliche Plugins eine Methode `STUDIP.go`, so wird mindestens eines der beiden Plugins weinen. Sinnvoll ist es da, den eindeutigen Klassennamen des Plugins zwischen zu schieben, entweder über `STUDIP.pluginclassname.go` oder eventuell auch einfach nur `pluginclassname.go`.
+
+## Eigene Stud.IP Bibliothek
+
+Für alle JavaScript-Programmierer wird von Interesse sein, dass einige Funktionen schon in Stud.IP implementiert sind, die auch an anderer Stelle nützlich sein könnten. Der Vorteil liegt auf der Hand: der Code bleibt klein und kann später besser um Funktionalitäten erweitert werden.
+
+### Vorhandene Methoden
+
+
+|Methode |Anwendbar auf |Verhalten |Zu beachten |
+---- | ---- | ---- | ---- |
+| $.showAjaxNotification(%%position=left%%); | Alle Elemente | Dem Element wird ein AJAX-Indikator vorangestellt. | Der Indikator kann mittels des Parameters %%position="right"%% auch hinter dem Element positioniert werden. Der Indikator wird absolut positioniert, was zu Problemen bei Veränderungen von Elementen in der Umgebung des eigentlichen Elements führen kann. |
+| $.hideAjaxNotification(); | Alle Elemente | Der dem Element zugehörende AJAX-Indikator wird entfernt, sofern vorhanden. | - |
+
+### Vorhandene Verhaltsmuster über CSS-Klassen
+
+
+|Klasse |Anwendbar auf |Verhalten |Zu beachten |
+| ---- | ---- | ---- | ---- |
+| .add_toolbar | <textarea /> | Dem Element wird eine Menüleiste mit vereinfachten Formatiermöglichkeiten vorangestellt. | Auf diese Art und Weise kann nur das Standard-Buttonset von Stud.IP verwendet werden. |
+| .load_via_ajax | <a /> | Eine angegebene URL (entweder *metadata.url* oder die URL des gegebenen Links) wird via AJAX in ein Element (entweder *metadata.target* oder das auf das gegebene Elemente folgende Element) geladen. | Über *metadata.indicator* kann via CSS-Regel das Element angegeben werden, welches den AJAX-Indikator erhält. Über *metadata* können weitere Parameter für den Aufruf der URL angegeben werden. |
+| .load_via_ajax.internal_message | <a /> | Spezialfall für interne Nachrichten. | Die Parameter werden an die Gegebenheiten in *lib/sms_func.inc.php* angepasst. |
+| .resizable | <textarea /> | Das Element kann durch einen Schieber am unteren Rand in der Höhe verändert werden. | - |
+
+#### AJAX-Anfragen
+
+AJAX-Anfragen sollten über jQuery mit den Methoden `[.load](http://api.jquery.com/load/)` `[.get](http://api.jquery.com/get/)` oder `[.ajax](http://api.jquery.com/jQuery.ajax/)` durchgeführt werden. Die meisten AJAX-Aufrufe haben einen AJAX-Indicator, der dem Nutzer mitteilt, dass gerade etwas geladen wird. Falls dieser Indicator ungebeten sein sollte, kann man die AJAX-Methode einbetten wie folgt:
+
+```JavaScript
+STUDIP.ajax_indicator = false;
+$('#dynamischer_bereich').load(url);
+STUDIP.ajax_indicator = true;
+```
+
+#### URLHelper in JavaScript
+
+Das Objekt `STUDIP.URLHelper` bietet für JavaScript ähnliche Funktionalität wie der [URLHelper in PHP](URLHelper). Dennoch darf man nicht vergessen, dass beide URLHelper gänzlich unabhängig voneinander sind und nicht miteinander kommunizieren können. Wozu braucht man dann einen URLHelper in JavaScript? Zum Beispiel, um:
+
+* Einen Link zu generieren, wo eine JavaScript-Datei sonst nicht wüsste, wie die Adresse des Servers ist. Schreiben Sie also `STUDIP.URLHelper.getURL("about.php")`, um einen URI-kodierten Pfad zu http://www.studip......de/about.php zu bekommen, oder `STUDIP.URLHelper.getURL("about.php")`, um das selbe als nicht URI-kodiert zu erhalten.
+* Variablen zu einer beliebigen URL hinzuzufügen, ohne sich Gedanken darüber zu machen, welche Variablen schon in der URL sind und welche nicht. Also wird aus `STUDIP.URLHelper.getURL(alte_url, {hallo: "welt"})` ein http:.../about.php?hallo=welt, egal ob alte_url schon einen Wert für hallo angegeben hatte oder auch nicht. Auch muss man sich dann keine Gedanken darüber machen, ob man den Parameter mit "?" oder einem "&" anhängt. Beachten Sie zudem, dass Parameter in der alte_url weniger Priorität haben als Parameter im zweiten Argument.
+* Variablen dauerhaft (also solange die HTML-Seite in Benutzung ist) zu generierten URLs hinzuzufügen. Das geht mit der Methode `STUDIP.URLHelper.addLinkParam("hallo", "welt")`. Nach diesem Aufruf wird jedes `STUDIP.URLHelper.getURL("about.php")` zum Beispiel http://..../about.php?hallo=welt zurück geben. Mit dieser Methode werden auch Parameter aus der abgegebenen URL überschrieben, also `STUDIP.URLHelper.getURL("about.php?hallo=ich")` würde trotzdem Welt als Inhalt des Parameters hallo wider geben. Nicht so jedoch mit `STUDIP.URLHelper.getURL("about.php", {hallo: "ich"})`, wobei der zweite Parameter wieder einmal Priorität hat.
+* Variablen dauerhaft von generierten URLs abzuziehen, wenn sie denn vorher drin gewesen sein sollten. Dazu gibt man analog zu oben addLinkParam("hallo", "") an und der Parameter hallo wird stets als zwingend leer angesehen und auch aus bestehenden URLs gestrichen, wenn vorher vorhanden.
+* Alle Hyperlinks eines Abschnittes des Dokumentes mit aktuellem Parameter zu versehen. Nach einem oder mehreren addLinkParam-Aufrufen könnte man `STUDIP.URLHelper.updateAllLinks("#container");` aufrufen, wodurch alle Links innerhalb des CSS-Selektors "#container" einmal durch die STUDIP.URLHelper.getURL(...) Methode ersetzt werden und dadurch aktuelle Parameter bekommen. Gibt man keinen Selektor an, werden die Links des ganzen Dokuments ersetzt.
+
+[#caching](#caching)
+#### Caching in JavaScript
+
+Stud.IP bietet seit Version 3.2 eine Abstraktion des Caching in JavaScript über `STUDIP.Cache` an. Man erhält eine Instanz mittels `STUDIP.Cache.getInstance()` mit dem optionalen Parameter `prefix`. Dieser Präfix sollte nach Möglichkeit immer genutzt und sinnvoll gewählt werden, da er Konflikte beim Zugriff auf Cachedaten verhindern kann. Ist der Präfix gesetzt, so ist gewährleistet, dass der Cache nur auf Daten "unterhalb" dieses Präfixes zugreifen kann, ohne einen entsprechenden Mechanismus bei jeder einzelnen Cacheoperation angeben zu müssen:
+
+```JavaScript
+let cache0 = STUDIP.Cache.getInstance('foo.');
+let cache1 = STUDIP.Cache.getInstance();
+
+cache0.set('test', 42);
+
+console.log(cache0.get('test'), cache1.get('foo.test'));
+
+cache1.set('foo.test', 23);
+
+console.log(cache0.get('test'), cache1.get('foo.test'));
+```
+
+Der Cache unterstützt folgende Operationen:
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|`has(key)`|Fragt ab, ob der Cache einen Wert für den Schlüssel hat |
+|`get(key, setter, expires)`|Holt einen Wert für den Schlüssel ab. Ist kein Wert gesetzt und `setter` ist definiert, so wird der Wert erzeugt und mit der angegebenen Laufzeit gespeichert.|
+|`set(key, value, expires)`|Speichert einen Wert für den angegebenen Schlüssel mit der angegebenen Laufeit (`expires = false` bedeutet, dass der Wert gelöscht wird sobald das Browserfenster geschlossen wird).|
+|`remove(key)`|Löscht den gespeicherten Wert für den angegebenen Schlüssel.|
+|`prune()`|Löscht alle gespeicherten Daten. |
+
+**Zu beachten**: Sollen Daten nur für einen Nutzer oder eine gewisse Session gespeichert werden, so sollte zwingend ein geeigneter Präfix genutzt werden, der die entsprechenden Daten (gehasht) enthält. Der Cache in JavaScript weiß nichts von den Gegebenheiten auf PHP-Seite.
+
+## Das jQuery-Framework in Stud.IP
+
+Zur Zeit (Stud.IP 4.1) wird jQuery 3.2.1 und jQuery-UI 1.12.1 verwendet. Von jQuery-UI sind alle Funktionen in Stud.IP geladen.
+
+
+### Weitere verwendete JS-Bibliotheken
+
+Eine Übersicht der aktuell verwendeten JS-Bibiliotheken findet sich in `package.json`.
+
+#### jQuery-Plugins
+
+##### TableSorter [(Link)](http://tablesorter.com)
+* [jquery.tablesorter.js](https://develop.studip.de/trac/browser/trunk/public/assets/javascripts/jquery.tablesorter.js?rev=19220)
+* [jquery.tablesorter.min.js](https://develop.studip.de/trac/browser/trunk/public/assets/javascripts/jquery.tablesorter.min.js?rev=19220)
+* [jquery.tablesorter.pager.js](https://develop.studip.de/trac/browser/trunk/public/assets/javascripts/jquery.tablesorter.pager.js?rev=19220) TableSorter-Pagination-Plugin
+
+Dieses Plugin stellt ähnliche Möglichkeiten wie das vormalige TableKit-Plugin (s.o.) zur Verfügung und ermöglicht das flexible client-seitige Sortieren von Tabellen.
+
+#### jQuery UI Multiselect [(Link)](http://www.quasipartikel.at/multiselect/)
+* [ui.multiselect.js](https://develop.studip.de/trac/browser/trunk/public/assets/javascripts/ui.multiselect.js?rev=19220)
+
+Das jQuery-UI-Multiselect-Plugin wandelt "multiple select inputs" in sexier aussehende Äquivalente um. Das Plugin wurde in den folgenden Changesets gepatchet:
+* [https://develop.studip.de/trac/changeset/18594/trunk/public/assets/javascripts/ui.multiselect.js r18594](https://develop.studip.de/trac/changeset/18594/trunk/public/assets/javascripts/ui.multiselect.js r18594)
+* [https://develop.studip.de/trac/changeset/18635/trunk/public/assets/javascripts/ui.multiselect.js r18635](https://develop.studip.de/trac/changeset/18635/trunk/public/assets/javascripts/ui.multiselect.js r18635)
+
+#### JS-L10n [(Link)](https://github.com/eligrey/l10n.js/)
+* [l10n.js](https://develop.studip.de/trac/browser/trunk/public/assets/javascripts/l10n.js?rev=19220)
+
+Diese Bibliothek wird verwendet, um lokalisierte Strings in JS verwenden zu können. Weiteres ist bereits im [Wiki](quickstart/Internationalisierung) dokumentiert.
+
+## FAQ
+
+#### Wie modularisiere ich meinen JavaScript-Code?
+In Stud.IP darf Code nach ECMAScript2015 und besser geschrieben werden, der dann zu ES5 kompiliert wird. Wenn ich meinen Code also auf mehrere Dateien verteilen möchte, verwende ich einfach das "import"-Statement, ein Sprachfeature von JavaScript, das gut auf MDN beschrieben wird: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
+
+Dazu lege ich also eine zweite Datei an und trage dann in meiner ersten Datei ein "import"-Statement mit dem relativen Pfad zu dieser Datei ein.
+
+#### Wie binde ich npm-Bibliotheken ein?
+Das kann man gut am Beispiel von "lodash" zeigen: Die lodash-Bibliothek wird via npm installiert: "npm i --save-dev lodash". Dann trage ich in meine Datei ein:
+```JavaScript
+import lodash from "lodash"
+```
+
+#### Was muss ich als Modulname bei `import` hinschreiben?
+
+Die exakten Details finden sich hier: https://webpack.js.org/concepts/module-resolution/#resolving-rules-in-webpack
+
+Hier eine kurze Zusammenfassung:
+
+Verweise ich auf eigenen Code, schreibe ich einen relativen Pfad auf:
+
+```JavaScript
+import '../src/file1';
+import './file2';
+```
+
+Möchte ich eine Bibliothek importieren, verwende ich den Modulnamen der Bibliothek:
+
+```JavaScript
+import lodash from 'lodash';
+import 'module/lib/file';
+```
+
+
+#### Ich möchte Code/Assets nur bei Bedarf laden. Wo muss ich die eintragen und wie lade ich die?
+
+In ECMAScript wird das dynamische Nachladen gerade standardisiert (Anfang 2019 ist diese Feature gerade in Stage 3) Der gegenwärtige Stand ist in https://github.com/tc39/proposal-dynamic-import dokumentiert.
+
+Dennoch kann man dank webpack jetzt schon damit arbeiten. Dazu lade ich mittels des funktionsartigen "import()"-Ausdrucks einfach eine Datei.
+
+Hier ein Beispiel:
+
+```JavaScript
+import('/modules/my-module.js')
+ .then((module) => {
+ // Do something with the module.
+ }).catch(error => 'An error occurred while loading the module');
+```
+
+Ausführliche Doku dazu gibt es hier:
+
+* https://webpack.js.org/guides/code-splitting/#dynamic-imports
+* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports
+
+#### Ich habe ein Plugin, das seine Komponenten auch gerne packen würde. Wie sage ich es dem Stud.IP-Kernsystem bzw. dessen Webpack?
+
+Plugins kümmern sich um ihre eigenen Angelegenheiten. Wenn ein Plugin-Entwickler webpack verwenden möchte, tut er das für sein Plugin selbst.
+
+#### Wie kann ich die gleiche Funktion zum Document Ready und beim Dialog Update eine Funktion ausführen?
+
+Ab Version Stud.IP 4.4 gibt es dafür den Event `studip-ready`, der die Events `ready` und `dialog-update` zusammenfasst und in beiden Fällen getriggert wird. Vor Version Stud.IP 4.4 muss die gleiche Funktion von Hand an die beiden Events gebunden werden.
+
+#### Welches ist der Worttrenner für JavaScript-Dateien?
+
+JavaScript-Dateien sollen im Kebab-Case-Stil abgelegt werden (also `eins-zwei.js` und nicht `eins_zwei.js`).
+
diff --git a/docs/docs/quickstart/message-box.md b/docs/docs/quickstart/message-box.md
new file mode 100644
index 0000000..dde6004
--- /dev/null
+++ b/docs/docs/quickstart/message-box.md
@@ -0,0 +1,60 @@
+---
+title: MessageBox
+sidebar_title: MessageBox
+---
+
+Die Messageboxen werden verwendet, um in Stud.IP Statusmeldungen jeglicher Art anzuzeigen.
+
+Die folgenden fünf Typen der Messagebox gibt es:
+
+| Parameter | Beschreibung |
+| ---- | ---- |
+| **exception** | Nur für Systemfehler. Wird von unhandledExceptions benutzt. |
+| **error** | Für Fehlermeldungen jeder anderen Art. Fehlende Benutzerrechte, falsche Eingaben etc. |
+| **warning** | Für sämtliche Dinge, die keine echten Fehler sind aber auch nicht einfach als Information/Hinweis abgetan werden sollten. |
+| **info** | Für allgemeine Hinweise, keine Ergebnisse bei Suchabfragen |
+| **success** | Für Erfolgsbestätigungen. Speicherung, Änderung usw. |
+
+### Parameter
+Es können mindestens 1 bis maximal 3 Parameter übergeben werden. Diese haben folgende Bedeutung:
+
+| Parameter | Beschreibung |
+| ---- | ---- |
+| `$message` | Die Hauptnachricht, die in der MessageBox angezeigt werden soll. |
+| `[$details]` | Der 2. Parameter ist optional für zusätzliche Informationen. Diese müssen als Array übergeben werden. |
+| `[$closed]` | Wenn dieser optionalen Parameter `true` übergeben wird, werden die zusätzlichen Details zugeklappt angezeigt. |
+
+### Messagebox auf der Folgeseite anzeigen
+
+Im Regelfall möchte man die Statusmeldung nicht auf der aktuellen Seite,
+sondern auf der folgenden Seite anzeigen.
+
+Dafür gibt es die Methode `PageLayout::postMessage()`, der eine MessageBox übergeben wird.
+
+Der Einfachheit halber gibt es für alle Typen der MessageBox eine passende `post<type>`-Methode der Klasse [`PageLayout`](PageLayout), wie beispielsweise `PageLayout::postSuccess()` oder `PageLayout::postError()`.
+
+Die Parameter der Methoden sind analog zu den oben beschriebenen Parametern der Methoden der Klasse `MessageBox`.
+
+
+### Funktionshinweise
+```php
+// Beispiel für eine einfache Info-Nachricht
+echo MessageBox::info('Nachricht');
+
+// Beispiel für eine Error-Nachricht mit zusätzlichen Details
+echo MessageBox::error('Nachricht', ['optional details', 'more details']);
+
+// Beispiel für eine Success-Nachricht mit zusätzlichen Details, die jedoch zugeklappt sind.
+echo MessageBox::success('Nachricht', ['optional details'], true);
+
+// Beispiel für eine Success-Nachricht auf der folgenden Seite
+PageLayout::postSuccess('Folgende Nutzer wurden angelegt', [
+ 'Max Mustermann',
+ 'John Doe',
+]);
+```
+
+
+### Screenshots
+
+![image](../assets/4bc68dbe8b01745976ff8b18aa025de6/image.png)
diff --git a/docs/docs/quickstart/modaler-dialog.md b/docs/docs/quickstart/modaler-dialog.md
new file mode 100644
index 0000000..e5c35c5
--- /dev/null
+++ b/docs/docs/quickstart/modaler-dialog.md
@@ -0,0 +1,171 @@
+## Modaler Dialog
+
+### Serverseitige erzeugte Abfragen
+
+Um einen modalen Dialog zu erzeugen, kann man ganz einfach die Methode `PageLayout::postQuestion();` verwenden. Diese Methode kapselt in sich die Erzeugung eines entsprechenden `QuestionBox`-Objekts und setzt die entsprechenden Parameter. Die Abfrage wird dann analog zu den [`MessageBoxen`](MessageBox) bei nächstmöglicher Gelegenheit im System angezeigt.
+
+Die `QuestionBox` kann die Antwort sowohl als `GET` als auch als `POST`-Request absetzen. Im Standardfall bei `PageLayout::postQuestion()` wird ein `POST`-Request abgesetzt, wodurch eine einfache Unterscheidung zwischen Bestätigung und Ablehnung der Frage schon alleine durch die genutzte Request-Methode erreicht werden kann.
+
+Die Funktion bzw. die Erzeugung einer `QuestionBox` benötigt mindestens 1, maximal 3 Parameter.
+
+#### Parameter
+* `$question`: Der Frage bzw. die Aktion, die bestätigt werden soll
+* `[$approveParams]`: optional, Link Parameter für den URLHelper im Falle einer positiven Antwort, in der Form `['name' => wert, 'name2' => wert2]`.
+* `[$disapproveParams]`: optional, Link Parameter für den URLHelper im Falle einer negativen Antwort.
+
+Die Rückgabe der Methode ist ein `QuestionBox`-Objekt, welches noch weiter manipuliert werden kann.
+
+#### Weitere Methoden der QuestionBox
+
+Das `QuestionBox`-Objekt stellt darüber hinaus noch weitere Methoden zur Verfügung:
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+| `setApproveParameters(array $parameters)` | Setzt die Link Parameter für die positive Bestätigung|
+| `setApproveURL($url)` | Setzt die URL, die bei einer positiven Bestätigung aufgerufen werden soll|
+| `setDisapproveParameters(array $parameters)` | Setzt die Link Parameter für die negative Bestätigung |
+| `setDisapproveURL($url)` | Setzt die URL, die bei einer negativen Bestätigung aufgerufen werden soll |
+| `setBaseURL($url)` | Setzt die URLs für die positive und die negative Bestätigung auf den gleichen Wert |
+| `setMethod($method)` | Setzt die zu nutzende Request-Methode (es ist anzuraten, immer `POST` zu nutzen; in dem Fall wird auch immer ein gültiges [CSRF-Token](CSRFProtection) in dem Request enthalten sein) |
+| `includeTicket()` | Weist die QuestionBox an, ein frisches Stud.IP-Ticket beim Rendern einzufügen |
+
+
+#### Beispiel
+```php
+PageLayout::postQuestion(_('Wollen Sie dies wirklich löschen?'), $accept_url = *, $decline_url = *);
+```
+
+#### Screenshot
+![image](../assets/fbce782c9fa1a8778926c5f6ade1d5d4/image.png)
+
+
+### Clientseitige Dialoge (*data-dialog"*)
+
+Die Ziele dahinter sind sowohl ein einheitliches Verhalten von Dialogen innerhalb von Stud.IP als auch eine Erleichterung für den Entwickler. Im Idealfall muss kein JavaScript mehr angefasst werden, um Dialoge zu nutzen. Die einzige Anpassung auf Serverseite ist das Auszeichnen des HTML mit entsprechenden Attributen und das Entfernen des umgebenden Layouts, so dass nur der wirklich relevante Inhalt zurückgegeben wird.
+
+#### Einbindung
+
+Dialoge können im HTML an den Tags `<a>`, `<button>` und `<form>` über das Attribut **`data-dialog`** gesteuert werden. Derart ausgezeichnete Elemente werden bei aktiviertem Javascript ihre Inhalte in einem modalen Dialog anzeigen. Die Inhalte werden dabei per AJAX nachgeladen und mittels jQuery UI's Dialog-Widget angezeigt. Auf Serverseite kann ein Aufruf, der aus einem solchen Dialog erfolgte, an dem HTTP-Header `X-Dialog` erkannt werden.
+
+Sollte bereits ein Dialog geöffnet sein und ein entsprechend ausgezeichnetes Element innerhalb des Dialogs aufgerufen werden, so wird der aktuelle Dialog aktualisiert, man verbleibt also im Dialog.
+
+Zu beachten: In der Rückgabe enthaltene `<script>`-Tags werden gefiltert und ausgeführt. Dies ist kein Standardverhalten von jQuery UI's Dialog und sollte beachtet werden (auch wenn derartiges Inline-Javascript im Idealfall vermieden und stattdessen auf globale Handler zurückgegriffen werden sollte).
+
+#### Parameter
+
+Der Dialog kann über verschiedene Attributangaben oder HTTP-Header gesteuert werden, welche im Folgenden erläutert werden. Dabei gilt, dass die Attributangaben auch beliebig miteinander kombiniert werden können, bspw. `data-dialog="title=foo;size=auto;buttons=false"`.
+
+##### Titel
+
+Der Titel eines Dialogs ist standardmässig der Inhalt des title-Attributs des zugrundeliegenden Elements (sofern vorhanden) und fällt bei den Tags `<a>` und `<button>` auf den Textinhalt des Elements zurück. Der Titel kann über verschiedene Parameter gesteuert werden:
+
+| Parameter | Beschreibung |
+| ---- | ---- |
+|`data-dialog="title='Test Titel'"` |Setzt den Titel auf **Test Titel** |
+|HTTP-Header `X-Title: Test Titel 2` |Setzt den Titel auf **Test Titel 2** |
+
+##### Größe
+
+Die Größe des Dialogs ist standardmässig 2/3 der Breite und Höhe und des Browserfensters. Dieser Wert kann über optionale Parameter gesteuert werden, wobei die minimale Größe des Dialogs auf 320x200 Pixel festgesetzt wurde:
+
+| Parameter | Beschreibung |
+| ---- | ---- |
+|`data-dialog="width=X"` |Setzt die Breite des Dialogs auf **X** Pixel |
+|`data-dialog="height=Y"` |Setzt die Höhe des Dialogs auf **Y** Pixel |
+|`data-dialog="size=XxY"` |Fasst die Angabe der Breite von **X** Pixeln und Höhe von **Y** Pixeln zusammen |
+|`data-dialog="size=X"` |Erzeugt einen quadratischen Dialog mit **X** Pixeln Breite und Höhe |
+|`data-dialog="size=auto"` |Versucht, die Größe des Dialogs an den geladenen Inhalt anzupassen. |
+
+##### Buttons
+
+Standardmässig enthält jeder Dialog einen Button *Abbrechen* am unteren Rande des Dialogs, welcher den Dialog schliesst.
+
+Es wird auch versucht, Buttons aus der Rückgabe zu extrahieren, damit diese sich ebenfalls in der Button-Leiste des Dialogs einreihen können. Dabei werden nur die Tags `<a>` und `<button>` berücksichtigt, welche entweder direkt mit dem Attribut `data-dialog-button` ausgezeichnet sind oder sich unterhalb eines Elements befinden, welche mit dem Attribut `data-dialog-button` ausgezeichnet wurde.
+
+Sowohl Links als auch Formulare können auf diese Weise aufgerufen werden. Dies bedeutet im Besonderen dass, sich auch ein Speichern-Button eines Formulars am unteren Rande des Dialogs befinden kann.
+
+Zu beachten ist, dass ein vorhandener Link/Button mit dem Inhalt *Abbrechen* mit dem Standardbutton überschrieben wird, welcher den Dialog schliesst. Dieses Verhalten ist gewollt und sollte beim Entwickeln berücksichtigt werden.
+
+Die Button-Leiste kann über die folgenden Mechanismen komplett ausgeschaltet werden, was auch bedeutet, dass die Buttons nicht aus dem zurückgegebenen Inhalt extrahiert werden:
+
+| Parameter | Beschreibung |
+| ---- | ---- |
+| `data-dialog="buttons=false"` | HTTP-Header `X-No-Buttons` |
+
+
+##### Weitere Optionen
+
+Ein Dialog kann über die folgenden Mechanismen geschlossen werden:
+
+| Parameter | Beschreibung |
+| ---- | ---- |
+| `data-dialog="close"`| HTTP-Header `X-Dialog-Close |
+| HTTP-Header `X-Location: <url>` | Bei der Auswertung einer Rückgabe kann auch ein Verweis auf eine andere Seite angegeben werden, welche den Dialog verlässt. Dies geschieht über folgenden Mechanismus: |
+| `data-dialog="reload-on-close"` | Die den Dialog umgebende Seite kann beim Schliessen des Dialogs automatisch neu geladen werden |
+| `data-dialog="resize=false"` |Der Dialog kann starr eingestellt werden, er kann also in der Größe nicht vom Nutzer verändert werden |
+| HTTP-Header `X-WikiLink: <url>` | Über den HTTP-Header `X-WikiLink` kann eingestellt werden, zu welcher Seite das im Titel angezeigte Hilfe-Icon verweisen soll: |
+| `data-dialog="center-content"` | Der Inhalt des Dialogs kann in sowohl horizontal als auch vertikal zentriert werden |
+| HTTP-Header `X-Dialog-Execute: <JS-Funktion, bspw. STUDIP.Foo.bar>` | Aus der Rückgabe heraus, kann eine beliebige JavaScript-Funktion aufgerufen werden, welcher der Body des Requests übergeben wird (falls dieser JSON ist, wird er entsprechend umgewandelt). Ist die übegebene JavaScript-Funktion ungültig (nicht definiert oder keine Funktion), so wird ein entsprechender Fehler geworfen. |
+| HTTP-Header `X-Dialog-Execute: {func: <JS-Funktion, bspw. STUDIP.Foo.bar>, payload: []}`| Alternativ kann in diesem Header ein JSON-kodiertes Array mit dem verpflichtendem Eintrag `func` als Funktionsnamen und dem optionalen Eintrag `payload` übergeben werden. Dies ist in Situationen notwendig, wo zwar der Dialog aktualisiert werden soll (als HTTML über den Body der AJAX-Response) aber auch über die angegebene Funktion `func` mittels des gelieferten Payloads Änderungen stattfinden sollen. |
+
+
+Über die **CSS-Klasse** `hide-in-dialog` können Inhalte gezielt in Dialogen versteckt werden.
+
+##### Unterstützte Events
+
+Beim Öffnen und Schliessen des Dialogs werden jeweils JavaScript-Events getriggert, um dem Entwickler die Möglichkeit zu geben, das Verhalten der Inhalte dynamisch zu erweitern/ändern.
+
+* Beim **Öffnen** wird der Event `dialog-open` getriggert
+* Beim **Öffnen** und beim **Ändern** des Inhalts des Dialogs via AJAX wird der Event `dialog-update` getriggert
+* Beim **Schliessen** wird der Event `dialog-close` getriggert
+
+Beiden Events wird der aktuelle Dialog sowie die Optionen beim Aufruf übergeben. Exemplarischer Beispielcode:
+
+```javascript
+(function ($) {
+ $(document).on('dialog-open', function (event, parameters) {
+ var dialog = parameters.dialog;
+ var options = parameters.options;
+
+ $(dialog).dialog('title', options.title + ' - adjusted');
+ });
+}(jQuery));
+```
+
+Beim Laden der Daten über AJAX wird nach dem Laden der Event `dialog-load` getriggert, welchem die Optionen und das verwendete jQuery-XMLHttp-Request (als `xhr`) übergeben wird.
+
+Je nachdem, wie der Dialog aufgerufen wurde, erfolgen die Events an unterschiedlichen Stellen:
+
+Wurde der Dialog implizit über das `data-dialog`-Attribut an einem Element geöffnet, werden die Events an eben diesem Element getriggert, während sie im expliziten/programmatischen Fall am globalen *document*-Objekt getriggert werden. Eine Ausnahme bildet der Event `dialog-update`. Dieser wird immer global am *document*-Objekt getriggert, damit er immer aufgerufen wird - unabhängig davon, ob das auslösende Element vorhanden ist oder nicht.
+
+### Clientseitige Dialoge zur Dateneingabe
+
+Dialoge, welche serverseitig zur Eingabe von Daten in Formularen geladen werden, sollen im Fehlerfall eine Fehlermeldung im Dialog anzeigen. Bei erfolgreicher Dateneingabe soll der Dialog geschlossen werden und die Seite, welche im Hintergrund des Dialoges sichtbar ist (und aus welcher der Dialog geladen wurde) neu geladen werden. Um dies zu ermöglichen, müssen folgende Dinge im Quellcode eingebaut werden:
+
+* Das Formular, welches im Dialog angezeigt wird, muss das data-dialog Attribut besitzen, welches den Wert "reload-on-close" besitzt.
+* Der Dialog wird über einen Link aufgerufen, bei dem ebenfalls das data-dialog Attribut mit dem Wert "reload-on-close" gesetzt ist.
+* In der Aktion im Controller wird im Fehlerfall eine Fehlermeldung via PageLayout::postError (oder PageLayout::postMessage(MessageBox::error())) ausgegeben.
+* Im Erfolgsfall wird in der Aktion im Controller der Header X-Dialog-Close hinzugefügt und nichts gerendert:
+
+```php
+<?php
+$this->response->add_header('X-Dialog-Close', '1');
+$this->render_nothing();
+```
+
+Damit wird das oben beschriebene Verhalten des Dialoges erreicht.
+
+### Clientseitige Abfragen
+
+Über die Methode `STUDIP.Dialog.confirm(question, yes_callback, no_callback);` kann eine Bestätigung einer Aktion abgefragt werden. Der Parameter `question` enthält den Text für die Bestätigung (wie bspw "Sind Sie sicher, dass Sie dieses Element löschen wollen?") und der `yes_callback` wird anschliessend aufgerufen, falls die Abfrage positiv bestätigt wurde. Der `no_callback` ist optional und würde in dem Fall aufgerufen werden, dass die Abfrage negativ bestätigt wird.
+Der Handler kann auch als [Deferred](http://api.jquery.com/category/deferred-object/) genutzt werden:
+
+```javascript
+STUDIP.Dialog.confirm('Sind Sie sicher?'.toLocaleString()).done(function () {
+ alert('Aktion wurde bestätigt');
+}).fail(function () {
+ alert('Aktion wurde nicht bestätigt');
+});
+```
+
+Als Frage kann auch ein boole'scher Wert übergeben werden, was dazu führt, dass die Abfrage sofort als bestätigt bzw. abgelehnt gehandhabt wird.
diff --git a/docs/docs/quickstart/navigation.md b/docs/docs/quickstart/navigation.md
new file mode 100644
index 0000000..a18f2b0
--- /dev/null
+++ b/docs/docs/quickstart/navigation.md
@@ -0,0 +1,255 @@
+---
+title: Navigationsverwaltung
+---
+
+Die Navigations-API dient zur Manipulation der globalen Navigationsstruktur in Stud.IP. Damit sollen dynamische Änderungen an der Navigation leichter möglich und gleichzeitig der Pflegeaufwand der zentralen Dateien reduziert werden. Der Begriff "Navigation" umfaßt dabei nicht nur die Reiternavigation, sondern unter anderem auch die Icons in der Kopfzeile -- im folgenden "Top-Navigation" genannt -- und die Links auf der persönlichen Startseite, sowie ggf. in der Info-Box vorhandene Navigationspunkte (sofern es dort welche gibt).
+
+### Allgemeines
+
+Die gesamte Navigation wird von der Klasse `Navigation` in Stud.IP verwaltet, d.h. Navigationspunkte werden generell als Objekte erzeugt. Jeder Navigationspunkt kann beliebig viele Unterpunkte besitzen, so daß die Menge aller Navigationspunkte zusammen einen Baum bildet (zumindest solange ein Punkt nur an einer Stelle in der Navigation eingehängt wird). Einzelne Navigationspunkte können über ihren Pfad im Baum angesprochen werden. Jedem Navigationobjekt kann ein Bild zugewiesen werden, allerdings werden derzeit nur für die Punkte der Top-Navigation diese auch angezeigt. Navigationspunkte, die keine URL besitzen, werden automatisch ausgeblendet. Das gleiche gilt für Punkte der Top-Navigation, die kein Bild gesetzt haben.
+
+Besitzt ein Navigationspunkt, der weitere Unterpunkte hat, keine eigene URL, so wird automatisch die URL des ersten Unterpunkts verwendet, der eine URL gesetzt hat. Das ist besonders dann praktisch, wenn der erste Punkt der Subnavigation je nach Rechtestufe manchmal nicht angezeigt wird. So verweist der übergeordnete Reiter immer automatisch auf den ersten sichtbaren Reiter der Subnavigation. Enthält die Subnavigation keine (sichtbaren) Elemente, wird der übergeordnete Reiter dann ebenfalls automatisch ausgeblendet.
+
+### Erzeugung eines Navigationspunktes
+
+Für die Erzeugung und Konfiguration eines Navigationsobjekts gibt es im wesentlichen vier Operationen:
+
+```php
+__construct($title, $url = NULL, $params = NULL)
+```
+Erzeugt einen Navigationspunkt und setzt Titel und (optional) URL und ggf. URL-Parameter.
+
+```php
+setTitle($title)
+```
+
+Setzt den Titel des Navigationsobjektes. Ob dieser angezeigt wird ist abhängig vom Platzierungsort des Navigationsobjektes.
+
+```php
+setURL($url, $params = NULL)
+```
+
+Setzt den Titel bzw. die URL. Weitere URL-Parameter können wie beim `URLHelper` als Array von Schlüssel/Wert-Paaren übergeben werden.
+
+```php
+setImage($image, $options = [])
+```
+Setzt ein Bild für den Navigationspunkt, entweder über den Pfad zu einer Bilddatei (z.B. in einem Plugin) oder über einen Bildnamen aus dem Assets-Bereich (der Normalfall im Kernsystem). Zusätzliche Attribute für den `img`-Tag können ebenfalls übergeben werden (*title*, *style* o.ä.). Die gesetzten Bilder werden auch in der jeweiligen Reiternavigation in Veranstaltungen und Instituten angezeigt.
+
+```php
+setActiveImage($image, $options = [])
+```
+
+Setzt ein Bild für den aktiven Zustand des Navigationspunkts, entweder über den Pfad zu einer Bilddatei oder über einen Bildnamen aus dem Assets-Bereich. Gibt es kein separates Bild für den aktiven Zustand, wird das normale Bild verwendet. Zusätzliche Attribute für den `img`-Tag können ebenfalls übergeben werden (*title*, *style* o.ä.). Die gesetzten Bilder auch in der jeweiligen Reiternavigation in Veranstaltungen und Instituten angezeigt.
+
+Ein kleines Beispiel:
+
+```php
+$navigation = new Navigation('Admin');
+
+$navigation->setURL('adminarea_start.php');
+$navigation->setImage(
+ 'header_admin',
+ ['title' => 'Zu Ihrer Administrationsseite')];
+```
+
+Beispiel für Bilder in der Reiternavigation
+![Bildschirmfoto_2021-11-12_um_14.12.17](../assets/31dccb807d8186506f761674e5637696/Bildschirmfoto_2021-11-12_um_14.12.17.png)
+
+### Einhängen eines Navigationspunktes im Plugin
+
+Benötigt ein Plugin einen Navigationspunkt, so wird dieser im Konstruktor des Plugins erzeugt und hinzugefügt. Die folgenden drei Codezeilen fügen bei einem SystemPlugin ein Navigationselement auf der Startseite hinzu:
+
+```php
+$navigation = new Navigation(dgettext('PluginName', 'Ein Plugin-Name'));
+
+$navigation->setURL(PluginEngine::getURL($this, [], 'controller/index'));
+
+Navigation::addItem('/start/pluginname', $navigation);
+```
+
+Unter der Annahme, das der Name des Plugins PluginName ist, würde das obige Beispiel auf der Startseite ein Navigationsobjekt erzeugen, welches auf den Pfad /public/plugins.php/pluginname/controller/index verweist.
+
+Die erste Zeile erzeugt ein Navigationsobjekt und gibt diesem einen Namen. Um eine einfache Übersetzung zu ermöglichen, wird mittels dgettext der Name übersetzbar gemacht. Danach wird die URL gesetzt, unter Zuhilfenahme der Funktion getURL der PluginEngine-Klasse. Diese benötigt zuerst die Plugin-Instanz, danach ein Array mit Parametern für die URL und zuletzt der Pfad unterhalb von plugins.php, auf welchen verwiesen werden soll. In der dritten Zeile wird nun zur Navigation der Startseite ein neuer Eintrag anhand des neuen Navigationsobjektes hinzugefügt. Der Pfad /start/pluginname gibt hierbei keine URL an, sondern benennt den neuen Eintrag in der Navigation lediglich mit einem eindeutigen Namen. Das Navigationsobjekt selbst sorgt für den passenden URL-Pfad.
+
+### Einhängen eines Navigationspunktes in einem Stud.IP Controller
+
+Neue Navigationspunkte, welche auf einen Stud.IP Controller verweisen, werden direkt in die Navigations-Klassen, welche den Navigationsbaum wiederspiegeln, geschrieben. Dazu existieren im Stud.IP Hauptverzeichnis im Unterordner /lib/navigation/ verschiedene Klassen für unterschiedliche Bereiche der Navigation, welche alle von Navigation.php abgeleitet sind. Die vergebenen Klassennamen entsprechen den Pfaden der ersten Navigationsebene.
+
+
+Das Einhängen in die vorhandene Navigationsstruktur erfolgt wahlweise entweder über die statische Methode `Navigation::addItem($path, $navigation)` oder über die Methode `addSubNavigation($name, $navigation)` des übergeordneten Navigationsobjekts. Entsprechend gibt es auch die Methode `Navigation::removeItem($path)` bzw. `removeSubNavigation($name)`, um einen Eintrag wieder zu entfernen. Die Methode `Navigation::insertItem($path, $navigation, $where)` fügt einen Eintrag an einer bestimmten Position in die vorhandene Navigation ein.
+
+Um zum Beispiel zu einer Veranstaltung einen neuen Reiter "Demo" mit zwei Unterpunkten "Test 1" und "Test 2" hinzuzufügen, würde man zunächst die entsprechenden Navigationsobjekte erzeugen:
+
+```php
+$demonav = new Navigation('Demo', 'dispatch.php/demo/index');
+// URL: dispatch.php/demo/index?test=1
+$test1nav = new Navigation('Test 1', 'dispatch.php/demo/index', array('test' => 1));
+// URL: dispatch.php/demo/index?test=2
+$test2nav = new Navigation('Test 2', 'dispatch.php/demo/index', array('test' => 2));
+```
+
+Im Anschluss werden diese in die globale Navigationsstruktur eingehangen:
+
+```php
+Navigation::addItem('/course/demo', $demonav);
+$demonav->addSubNavigation('test1', $test1nav);
+$demonav->addSubNavigation('test2', $test2nav);
+```
+
+Alternativ kann das Einhängen auch folgendermaßen funktionieren:
+
+```php
+Navigation::addItem('/course/demo', $demonav);
+Navigation::addItem('/course/demo/test1', $test1nav);
+Navigation::addItem('/course/demo/test2', $test2nav);
+```
+
+Zum Entfernen eines Links wird removeItem wie folgt verwendet. Im Beispiel wird der Link "Meine Veranstaltungen" von der Startseite entfernt:
+
+```php
+Navigation::removeItem('/start/my_courses');
+```
+
+Das folgende Beispiel leitet den Punkt "Hochladen des persönlichen Bildes" der Homepage auf eine eigene Seite in einem Plugin um:
+
+```php
+$navigation = Navigation::getItem('/homepage/bild');
+$navigation->setURL(PluginEngine::getURL($plugin));
+```
+
+#### Aktivierung der Navigation
+
+Der auf der jeweils aktuellen Stud.IP-Seite "aktive" Navigationspunkt muss markiert werden, damit die korrekte Reiternavigation ausgewählt und dieser Punkt dort entsprechend hervorgehoben werden kann. Die "alte" Methode, die globale Variable `$reiter_view` zu setzen, funktioniert nun nicht mehr. Stattdessen muss man einen Navigationspunkt explizit aktivieren:
+
+```php
+Navigation::activateItem('/course/demo/test1');
+```
+
+Alternativ kann man auch statt der Klasse `Navigation` die Klasse `AutoNavigation` verwenden, die einen Navigationspunkt *automatisch* als aktiviert meldet, wenn die URL der aktuellen Seite der URL der Navigation entspricht und alle im Navigationsobjekt gesetzten URL-Parameter auch im aktuellen Request vorhanden sind und dort den gleichen Wert haben. Im oben beschriebenen Beispiel sähe das dann so aus:
+
+```php
+$demonav = new Navigation('Demo', 'demo.php');
+$test1nav = new AutoNavigation('Test 1', 'demo.php', array('test' => 1));
+$test2nav = new AutoNavigation('Test 2', 'demo.php', array('test' => 2));
+
+[...]
+```
+
+### Globale Navigationsstruktur
+
+Die globale Struktur der Navigation sieht derzeit so aus:
+
+* **/** [-Wurzelknoten der Navigation-]
+ * **start** [-persönliche Startseite-] (`StartNavigation`)
+ * **my_courses** [-meine Veranstaltungen-]
+ * ... ...
+ * **browse** [-Veranstaltungen und Einrichtungen-] (`BrowseNavigation`)
+ * **my_courses** [-meine Veranstaltungen-]
+ * **courses** [-Veranstaltungen Suchen / Hinzufügen-]
+ * ... ...
+ * **course** [-gewählte Veranstaltung / Einrichtung-] (`CourseNavigation`)
+ * **main** [-Übersicht-]
+ * **details** [-Kurzinfo-]
+ * **admin** [-Administration der Veranstaltung (nur für Dozenten und aufwärts)-]
+ * **leave** [-Austragen aus der Veranstaltung (nur für Autoren)-]
+ * **admin** [-Verwaltung (nur für Dozenten und Tutoren)-]
+ * **details** [-Grunddaten-]
+ * **study_areas** [-Studienbereiche-]
+ * **dates** [-Zeiten/Räume-]
+ * **admission** [-Zugangsberechtigungen-]
+ * **aux_data** [-Zusatzangaben-]
+ * **copy** [-Veranstaltung kopieren-]
+ * **archive** [-Veranstaltung archivieren-]
+ * **visibility** [-Sichtbarkeit ändern-]
+ * **news** [-Nachrichten einstellen-]
+ * **vote** [-Umfragen & Tests verwalten-]
+ * **evaluation** [-Evaluationen-]
+ * **forum** [-Forum-]
+ * **view** [-Themenansicht-]
+ * **unread** [-Neue Beiträge-]
+ * **recent** [-Letzte Beiträge-]
+ * **search** [-Suchen-]
+ * ***create_topic*** [-Neues Thema anlegen (nur für Tutor und Dozent)-]
+ * **members** [-TeilnehmerInnen (nur in Veranstaltungen)-]
+ * **view** [-TeilnehmerInnen-]
+ * **aux_data** [-Zusatzangaben (nicht immer aktiviert)-]
+ * **view_groups** [-Funktionen / Gruppen-]
+ * ***edit_groups*** [-Funktionen / Gruppen verwalten (nur für Tutor und Dozent)-]
+ * **faculty** [-Personal (nur in Einrichtungen)-]
+ * **view** [-MitarbeiterInnen-]
+ * ***edit_groups*** [-Funktionen / Gruppen verwalten (nur für Tutor und Dozent)-]
+ * **files** [-Dateibereich-]
+ * **tree** [-Baumstruktur-]
+ * **all** [-Alle Dateien-]
+ * **schedule** [-Ablaufplan-]
+ * **all** [-Alle Termine-]
+ * **type1** [-Sitzungstermine-]
+ * **other** [-Andere Termine-]
+ * ***topics*** [-Ablaufplan bearbeiten (nur für Tutor und Dozent)-]
+ * **scm** [-Freie Informationen (einstellbarer Tabname)-]
+ * ... [-eine beliebige Anzahl an Tabs mit MD5_id als Pfadnamen-]
+ * **literatur** [-Literatur-]
+ * **view** [-Literatur-]
+ * **print** [-Druckansicht-]
+ * ***edit*** [-Literatur bearbeiten (nur für Tutor und Dozent)-]
+ * **wiki** [-Wiki-]
+ * **show** [-WikiWikiWeb-]
+ * **listnew** [-Neue Seiten-]
+ * **listall** [-Alle Seiten-]
+ * **export** [-Export-]
+ * **resources** [-Ressourcen-]
+ * **overview** [-Übersicht-]
+ * **group_schedule** [-Übersicht Belegung-]
+ * **view_details** [-Details-]
+ * **view_schedule** [-Belegung-]
+ * **edit_assign** [-Belegung bearbeiten-]
+ * ... [-... Plugins und eLearning-Module-]
+ * **messaging** [-systeminterne Nachrichten-] (`MessagingNavigation`)
+ * ... ...
+ * **community** [-Wer ist Online-] (`CommunityNavigation`)
+ * **adress_book** [-Adressbuch-]
+ * **chat** [-Chat-]
+ * **studygroups** [-Studiengruppen-]
+ * **score** [-Rangliste-]
+ * **profile** [-Profilseite-] (`ProfileNavigation`)
+ * **view** [-persönliche Homepage-]
+ * **avatar** [-Hochladen des persönlichen Bildes-]
+ * ... ...
+ * **calendar** [-Termine und Stundenplan-](`CalendarNavigation`)
+ * **calendar** [-Terminkalender-]
+ * **schedule** [-Stundenplan-]
+ * **search** [-Die neue Suchseite-] (`SearchNavigation`)
+ * **courses**
+ * **archiv**
+ * **studygroups**
+ * **persons**
+ * **institutes**
+ * **literature**
+ * **resources**
+ * **tools** [-Die neue Werkzeugseite-] (`ToolsNavigation`)
+ * **news**
+ * **votes**
+ * **literature**
+ * **elearning**
+ * **admin** [-Administrationsbereich-] (`AdminNavigation`)
+ * **course** [-Veranstaltungen-]
+ * **config** [-globale Einstellungen-]
+ * ... ...
+ * **links** [-Link-Liste oben rechts-]
+ * **account** [-Kontoeinstellungen des Benutzers-]
+ * **search** [-Suche-]
+ * **logout** [-Logout-]
+ * ... ...
+ * **login** [-Menü auf der Login-Seite-] (`LoginNavigation`)
+ * **login** [-Login-]
+ * **register** [-Registrieren-]
+ * ... ...
+ * **footer** [-Fußzeile-] (`FooterNavigation`)
+ * **help** [-Login-]
+ * **sitemap** [-Sitemap-]
+ * **stud.ip** [-Stud.IP Webseite-]
+ * **blog** [-Blog-]
+ * **impressum** [-Impressum-]
+
+Anmerkung: Die Punkte "course", "links" und "login" besitzen kein Symbol in der Top-Navigation.
diff --git a/docs/docs/quickstart/ordnerstruktur.md b/docs/docs/quickstart/ordnerstruktur.md
new file mode 100644
index 0000000..1373d79
--- /dev/null
+++ b/docs/docs/quickstart/ordnerstruktur.md
@@ -0,0 +1,85 @@
+---
+title: Ordnerstruktur
+---
+
+Der Stud.IP Verzeichnisbaum beherbergt eine Menge von Dateien in einigen Unterordnern des Hauptverzeichnisses. Die Funktion der einzelnen Unterordner (und ggf. deren Unterordner) werden in diesem Artikel erläutert.
+
+### app
+
+Hier sind bereits auf [Trails](Trails) umgestellte Seiten enthalten.
+
+#### controllers
+
+Der Unterordner "controllers" in "app" beherbergt Trails-Controller für alle Stud.IP-Seiten, welche über Trails geladen werden.
+
+#### views
+
+Zu jedem Controller gehört eine Ansicht (view), welche in diesem Unterordner von "app" gespeichert wird.
+
+### cli
+
+PHP-Skripte zur Benutzung von Stud.IP auf der Kommandozeile sind in diesem Ordner enthalten.
+
+### config
+
+Konfigurationsdateien, inklusive der Vorlagen der beiden Haupt-Konfigurationsdateien config_local.inc.php und config.inc.php werden hier abgelegt.
+
+### data
+
+Hierdrin werden Dateien abgespeichert, welche nicht im Web-Root des Webservers liegen sollten und somit nicht direkt über den Webserver abrufbar sind.
+
+### db
+
+Hierin befinden sich SQL-Skripte, mit denen eine Stud.IP-Datenbank neu aufgesetzt werden kann. Zusätzlich sind Skripte mit Demo-Daten und Migrationsskripte für ältere Stud.IP-Versionen enthalten.
+
+### doc
+
+Dokumentationen zur Installation von Stud.IP.
+
+### lib
+
+Module und Bibliotheken von Stud.IP sind hierin enthalten. Dieser Ordner hat eine Reihe wichtiger Unterordner:
+
+#### classes
+
+Enthält Klassendefinitionen für Objekte, welche nicht in der Datenbank abgelegt werden.
+
+#### models
+
+Hier sind die meisten SimpleORMap (SORM) Datenbankmodelle abgespeichert.
+
+#### navigation
+
+Die verschiedenen Arten von Navigationsobjekten sind in diesem Ordner abgelegt.
+
+#### plugins
+
+Die Definitionen der Plugin-Schnittstelle sind hierin enthalten.
+
+#### locale
+
+Dieser Ordner enthält die Übersetzungsdateien von Stud.IP, sowie Skripte für die Unix-Shell, welche das automatische Erstellen der Übersetzungsdateien für Stud.IP erleichert.
+
+#### public
+
+Hier sind Dateien enthalten, welche direkt über den Webserver geladen werden können. Außerdem sind die wichtigsten Skripte (dispatch.php, plugins.php, ...) des Stud.IP-Systems in diesem Ordner enthalten. Der Ordner hat drei Unterordner.
+
+##### assets
+
+In diesem Unterordner von "public" sind Schriftarten, Bilder (inklusive Icons), JavaScript-Dateien, Sounddateien und Stylesheet-Dateien enthalten, welche beim Laden einer Stud.IP-Seite einfach mitgeladen werden können.
+
+##### pictures
+
+Verschiedene Hintergrundbilder für Seitenleisten oder bestimmte Elemente auf einer Seite.
+
+##### plugins_packages
+
+Hier werden Plugins abgelegt. Für jede Herkunftsbeschreibung eines Plugins ("origin" in der plugin.manifest Datei) wird ein eigener Unterordner angelegt, in welchem dann das Plugin abgelegt wird.
+
+#### templates
+
+Templates für Seiten, welche noch nicht auf [Trails](Trails) umgestellt wurden.
+
+#### vendor
+
+Bibliotheken, welche von externen Entwicklern entwickelt wurden und in Stud.IP benötigt werden, sind in diesem Ordner enthalten.
diff --git a/docs/docs/quickstart/page-layout.md b/docs/docs/quickstart/page-layout.md
new file mode 100644
index 0000000..d651aed
--- /dev/null
+++ b/docs/docs/quickstart/page-layout.md
@@ -0,0 +1,186 @@
+Über die Klasse PageLayout steht eine API in Stud.IP zur Verfügung, die verschiedene Anpassungen an der HTML-Grundstruktur der Ausgabe ermöglicht. Diess umfaßt einfache Dinge wie das Setzen des Seitentitels, ermöglicht aber auch das Hinzufügen oder Entfernen von HTML-Elementen im `<head>`-Bereich der Seite, um beispielsweise eigene Style-Sheets oder JavaScipt-Dateien einbinden zu können.
+
+## Die Klasse `PageLayout`
+
+Die Anpassung der HTML-Grundstruktur passiert über die neue Klasse `PageLayout`. Dazu bietet die Klasse eine Reihe statischer Methoden, die die verschiedenen Möglichkeiten abdecken.
+
+### Seitentitel
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+| **setTitle($title)** | Setzt den aktuellen Seitentitel, sowohl für die Anzeige im Browserfester als auch in Stud.IP. |
+
+Beispiel:
+```php
+PageLayout::setTitle(_('Startseite'));
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+| **getTitle()** | Liefert den aktuellen Seitentitel zurück. |
+| **hasTitle()** | Fragt ab, ob für die aktuelle Seite ein Seitentitel gesetzt wurde. |
+
+
+### Hilfe
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+| **setHelpKeyword($help_keyword)** | Setzt das Hilfe-Thema für die angezeigte Seite. Dieses wird dann beim Aufruf der Hilfe-Funktion an den Hilfe-Server übermittelt. |
+
+
+Beispiel:
+```php
+PageLayout::setHelpKeyword('Basis.Startseite');
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+| **getHelpKeyword()** | Liefert das eingestellte Hilfe-Thema zurück. |
+
+
+#### Reiternavigation
+| Funktion | Beschreibung |
+| ---- | ---- |
+| **setTabNavigation($path)** | Setzt den Pfad im Navigationsbaum, an dem die Reiternavigation startet. Es werden dann die beiden Ebenen unterhalb des angegebenen Navigationspunkts als Reiter (1. und 2. Ebene) angezeigt. Die Voreinstellung ist das jeweils aktive Element der Hauptnavigation. Ein explizites Setzen ist nur für Navigationskontexte mit Reiteranzeige notwendig, die an anderer Stelle als der Hauptnavigation eingebunden sind (wie z.B. das Impressum). Man kann auch die Anzeige der Reiternavigation ganz ausschalten, wenn man `NULL` als *$path* übergibt. |
+
+Beispiel:
+```php
+PageLayout::setTabNavigation('/links/siteinfo');
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+| **getTabNavigation()** | Liefert die Reiternavigation zurück. |
+
+### Hinzufügen von Inhalten
+
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+| **addStyle($content)** | Fügt eine neues CSS Style Element in den Seitenkopf ein. |
+
+Beispiel:
+```php
+PageLayout::addStyle('#highlight { background-color: red; }');
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**addStylesheet($source, $attributes = [])** | Fügt einen Verweis auf ein Style-Sheet in den Seitenkopf ein. *$source* kann entweder eine komplette URL oder ein Dateiname sein, der relativ zum Assets-Verzeichnis aufgelöst wird. Optional können weitere Attribute für das LINK-Element übergeben werden. |
+
+Beispiel:
+```php
+PageLayout::addStylesheet('print.css', ['media' => 'print']);
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**addScript($source)** | Bindet eine weitere JavaScript-Datei in den Seitenkopf ein. *$source* kann entweder eine komplette URL oder ein Dateiname sein, der relativ zum Assets-Verzeichnis aufgelöst wird. |
+
+Beispiel:
+```php
+PageLayout::addScript($this->getPluginURL() . '/vote.js');
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**addHeadElement($name, $attributes = [], $content = NULL)** | Fügt eine beliebiges HTML-Element in den Seitenkopf ein. *$name*, *$attributes* und *$content* entsprechen den Namen, der Attributliste und dem Inhalt des erzeugten Elements. Ist *$content* `NULL`, so wird das Element nicht abgeschlossen (wie META oder LINK), andernfalls wird automatisch auch ein schließendes Tag hinter dem Inhalt ausgegeben (z.B. bei SCRIPT). |
+
+Beispiel:
+```php
+PageLayout::addHeadElement('link', [
+ 'rel' => 'alternate',
+ 'type' => 'application/rss+xml',
+ 'title' => 'RSS',
+ 'href' => $feed_url,
+]);
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**addBodyElements($html)** | Fügt ein beliebiges HTML-Fragment direkt zu Beginn des BODY in die Seitenausgabe ein. Das ist vor allem in Plugins verwendbar, die Inhalte auf beliebigen Stud.IP-Seiten ausgeben wollen. |
+
+### Entfernen von Inhalten
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**removeStylesheet($source, $attributes = [])** | Entfernt einen Verweis auf ein Style-Sheet wieder aus dem Seitenkopf. *$source* kann wie bei **addStylesheet** entweder eine komplette URL oder ein Dateiname sein, der relativ zum Assets-Verzeichnis aufgelöst wird. |
+
+Beispiel:
+```php
+PageLayout::removeStylesheet('style.css');
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**removeScript($source)** | Entfernt eine eingebundene JavaScript-Datei wieder aus dem Seitenkopf. *$source* kann wie bei **addScript** entweder eine komplette URL oder ein Dateiname sein, der relativ zum Assets-Verzeichnis aufgelöst wird. |
+|**removeHeadElement($name, $attributes = [])** | Entfernt alle Elemente mit dem angegebenen Namen und den Attributen wieder aus dem Seitenkopf. |
+
+Beispiel:
+```php
+PageLayout::removeHeadElement('link', ['rel' => 'stylesheet']); // remove all style sheets
+```
+
+### Darstellung von Meldungen
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**postMessage(MessageBox $message)** | Veranlaßt das System, das angegebene [`MessageBox`-Objekt](MessageBox) bei nächster Gelegenheit anzuzeigen, d.h. bei der nächsten Ausgabe eines Layouts. Die Meldung bleibt so lange gespeichert, bis sie angezeigt wurde, auch über (ggf. mehrere) Redirects hinweg. |
+
+Für jeden Typen der `MessageBox` gibt es auch eine eigene `post<type>`-Methoden am `PageLayout`-Objekt, wie beispielsweise `PageLayout::postSuccess()` oder `PageLayout::postError()`.
+
+Beispiel:
+```php
+PageLayout::postMessage(MessageBox::success('Eintrag gelöscht'));
+// Äquivalent:
+PageLayout::postSuccess('Eintrag gelöscht');
+```
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**clearMessages()** | Löscht alle Meldungen, die zur Anzeige hinterlegt und noch nicht ausgegeben wurden. |
+
+### Bestätigen von Aktionen
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**postQuestion($question, $accept_url = "", $decline_url = "")** | Holt eine Bestätigung des Nutzers zu einer bestimmten Aktion ein. Wird die Ausführung der Aktion bestätigt, so wird ein POST-Request auf die angegebene `$accept_url` abgesetzt, im anderen Fall wird die `$decline_url` über GET aufgerufen. Weitere Details finden sich im ersten Abschnitt unter [Modaler Dialog](ModalerDialog#server). Der Mechanismus funktioniert analog wie `postMessage()`, so dass die Bestätigung bei der nächsten Gelegenheit dargestellt wird. |
+
+Beispiel:
+```php
+PageLayout::postQuestion(
+ 'Wollen Sie diese Aktion wirklich ausführen?',
+ URLHelper::getURL('dispatch.php/foo/confimed')
+);
+```
+
+### Anzeige des Seitenkopfs
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**disableHeader()** | Unterdrückt die Anzeige des Seitenkopfs mit dem Navigationsbereich, z.B. für eine Druckansicht (die sollte aber besser mit einem Print-Style-Sheet gelöst werden) oder ein Popup-Fenster. |
+
+### Setzen des Id-Attributs des Elements `<body>`
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**setBodyElementId($id)** | Setzt die Id des `<body>`-Elements, um über dieses beispielsweise in CSS oder Javascript gezielter Elemente ansprechen zu können.
+| **getBodyElementId()** | Liefert die gesetzte Id des `<body>`-Elements zurück. Wurde keine Id gesetzt, wird `false` zurückgeliefert. |
+
+### Ersetzen der Schnellsuche
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+|**addCustomQuicksearch($html)** | Ersetzt die Schnellsuche (oben rechts) durch beliebiges HTML.
+| **hasCustomQuicksearch()** | Fragt ab, ob die Schnellsuche ersetzt wurde.
+| **getCustomQuicksearch()** | Liefert den HTML-Code zurück, der die Schnellsuche ersetzen soll. Wurde kein HTML durch `addCustomQuicksearch()` gesetzt, liefert diese Methode `null` zurück. |
+
+## Beispiel
+
+Zum Abschluß noch ein kleines Beispiel aus einem Plugin, das (u.a.) eine eigene CSS-Datei mitbringt:
+
+```php
+PageLayout::setTitle('Neueste Aktivitäten');
+PageLayout::setHelpKeyword('Plugins.Activities');
+PageLayout::addStylesheet($this->getPluginURL() . '/css/activities.css');
+```
diff --git a/docs/docs/quickstart/rechtestufen.md b/docs/docs/quickstart/rechtestufen.md
new file mode 100644
index 0000000..3d07a71
--- /dev/null
+++ b/docs/docs/quickstart/rechtestufen.md
@@ -0,0 +1,6 @@
+---
+title: Rechtestufen
+---
+
+
+* TODO: Wo muss auf welche Rechtestufen gecheckt werden?
diff --git a/docs/docs/quickstart/request.md b/docs/docs/quickstart/request.md
new file mode 100644
index 0000000..695d95d
--- /dev/null
+++ b/docs/docs/quickstart/request.md
@@ -0,0 +1,125 @@
+---
+id: request
+title: Request
+sidebar_label: Request
+---
+
+
+## Benutzung der Klasse Request
+
+### Allgemeines
+
+Die Klasse `Request` soll den Zugriff auf Request-Parameter vereinheitlichen und vereinfachen. Sie ermöglicht das typsichere Abfragen von Werten (z.B. als Zahl oder Optionswert) und liefert unabhängig von den Einstellungen von `register_globals` und `magic_quotes_gpc` immer gleiche Ergebnisse. Außerdem gibt es eine Reihe von Hilfsfunktionen zur Abfrage von Request-Eigenschaften wie der aktuellen URL, des Server-Namens oder der Request-Methode. Im Unterschied zu `$_REQUEST` wertet die Klasse nur GET- und POST-Parameter aus, aber keine Cookies.
+
+Für den Zugriff auf die Parameter gibt es generell zwei Möglichkeiten: Direkten Zugriff über statische Methoden der Klasse (wie `Request::get($param)`) und alternativ Array-Zugriff über eine Instanz der Klasse (siehe `Request::getInstance()`). Letzteres braucht man vor allem dann, wenn man die Liste aller Parameter durchlaufen oder an eine Funktion übergeben möchte.
+
+### Verwendung der Klasse `Request`
+
+#### Abfragen von Request-Parametern
+
+Parameter aus dem aktuellen Request (d.h. GET- und POST-Parameter) sollten in Stud.IP über die Methoden der `Request`-Klasse abgefragt werden. Diese kann eine Validierung der Parameter vornehmen - z.B. daß es sich bei dem übergebenen Wert tatsächlich um eine Zahl handelt - und stellt sicher, daß die Ergebnisse unabhängig von PHP-Einstellungen wie `magic_quotes_gpc` sind. Für verschiedene Typen gibt es jeweils spezifische Abfragefunktionen.
+
+Die folgende Methoden dienen zum typsicheren Zugriff auf Request-Parameter, die skalare Werte enthalten. Falls es keinen Parameter mit dem angegeben Namen gibt (oder der Parameter im Aufruf den falschen Typ hat), wird der Wert `NULL` zurückgeliefert bzw. der übergebene Vorgabewert, sofern dieser angegeben wurde:
+
+| Funktion | Beschreibung |
+| ---- | ---- |
+| `get($param, $default = NULL)` | Liefert den Wert eines Parameters als String. **Vorsicht**: Der Wert wird so geliefert, wie vom Nutzer angegeben.|
+| `username($param, $default = NULL)`| Liefert den Wert eines Parameters als Nutzerkennung. Eine Nutzerkennung besteht nur aus Buchstaben und Ziffern sowie den Zeichen "_", "@", "." und "-". |
+| `option($param, $default = NULL)`| Liefert den Wert eines Parameters als Optionswert. Ein Optionswert besteht nur aus Buchstaben, Ziffern und Unterstrichen. |
+| `int($param, $default = NULL)`| Liefert den Wert eines Parameters als ganzzahligen Wert. |
+| `float($param, $default = NULL)` | Liefert den Wert eines Parameters als Gleitkommazahl. |
+
+Einige Beispiele:
+
+```php
+if (!Request::submitted('reset')) {
+ $title = Request::get('title'); // title can contain any characters
+ $inst_id = Request::option('inst_id'); // IDs are always alphanumeric
+ $sem_id = Request::option('sem_id');
+ $page = Request::int('page', 1); // page number is an integer
+}
+
+$days = Request::int('days', 14); // default to 14 days
+$category = Request::option('category'); // like "wiki", "forum" or "news"
+$enable = Request::int('enable');
+```
+
+Analog dazu gibt es auch Methoden zum typsicheren Zugriff auf Request-Parameter, die ein Array als Wert enthalten (z.B. eine Liste von Nutzerkennungen). Die Behandlung der einzelnen Werte passiert ganz analog zu den entsprechenden Methoden für skalare Werte. Falls es keinen Parameter mit dem angegeben Namen gibt, wird hier jeweils ein leeres Array zurückgeliefert:
+
+* `getArray($param)`
+* `usernameArray($param)`
+* `optionArray($param)`
+* `intArray($param)`
+* `floatArray($param)`
+
+Beispiel:
+
+```php
+$institutes = Request::optionArray('institutes');
+$is_enabled = Request::intArray('is_enabled');
+```
+
+#### Auflisten von Request-Parametern
+
+Möchte man die komplette Liste der Request-Parameter durchlaufen, so kann man dies über eine Instanz der `Request`-Klasse tun. Diese ist dann genau so zu verwenden wie das Array `$_REQUEST`, sie verhält sich allerdings immer so, als wäre *magic_quotes_gpc* ausgeschaltet:
+
+* `getInstance()`
+
+ Liefert die Singleton-Instanz der Request-Klasse. Über dieses Objekt kann man direkt mit Array-Notation auf die aktuellen Request-Parameter zugreifen oder mittels einer `foreach`-Schleife diese iterieren. Die oben aufgelisteten typsicheren Zugriffsmethoden können auch über das Objekt aufgerufen werden.
+
+Beispiel:
+
+```php
+$request = Request::getInstance();
+
+$user = $request['user']; // alternativ: $request->get('user')
+$mode = $request['mode']; // alternativ: $request->option('mode')
+
+foreach ($request as $key => $value) {
+ [...]
+}
+```
+
+#### Auswerten von Schaltflächen in Formularen
+
+Für die Auswertung, ob eine bestimmte Schaltfläche in einem Formular angeklickt wurde, gibt es eine spezielle Funktion (der Name des tatsächlich übergebenen Parameters ist nicht in jedem Browser identisch). Hierbei ist der Name der Schaltfläche anzugeben:
+
+* `submitted($param)`
+
+ Testet, ob ein Formular-Button (INPUT bzw. BUTTON) mit dem übergebenen Namen angeklickt wurde.
+
+Beispiel:
+
+```php
+if (Request::submitted('add_user')) {
+ $cmd = 'add_user';
+}
+```
+
+#### Auswertung von Request-Eigenschaften
+
+Es gibt noch eine Reihe von weiteren Hilfsfunktionen, um allgemeine Eigenschaften des Request abzufragen.
+Dazu gehört die aktuelle URL sowie der Name des Servers oder die Request-Methode (typischerweise `GET` oder `POST).
+Die wichtigsten davon sind hier kurz aufgelistet:
+
+* `url()`
+
+ Liefert die komplette URL der aktuellen Seite.
+
+* `method()`
+
+ Liefert die zum Aufruf verwendete Request-Methode (`GET`, `POST`, `HEAD` o.ä.).
+
+* `isAjax()`
+
+ Fragt ab, ob es sich um einen Ajax-Request (d.h. `XmlHttpRequest` von jQuery oder prototype) handelt.
+
+Beispiel:
+
+```php
+if (Request::isAjax()) {
+ $this->set_layout(null);
+}
+```
+
+Weitere Details befinden sich in der zugehörigen [API-Dokumentation](http://hilfe.studip.de/api/class_request.html).
diff --git a/docs/docs/quickstart/responsive-design.md b/docs/docs/quickstart/responsive-design.md
new file mode 100644
index 0000000..fae6359
--- /dev/null
+++ b/docs/docs/quickstart/responsive-design.md
@@ -0,0 +1,160 @@
+---
+title: Responsive Design
+---
+
+Damit Stud.IP auch für Endgeräte mit kleinen Bildschirmgrößen verwendbar sein kann, werden ein paar "media queries" verwendet, um sinnvolle "breakpoints" für Layout und GUI zu Verfügung zu stellen. Diese "breakpoints" unterscheiden sich nur in der minimalen Breite des "viewports", also die Breite des virtuellen Fensters, in das der (mobile) Browser die Seite hineinrendert. Abhängig von dieser Breite können Elemente passend skaliert oder überhaupt gezeigt bzw. versteckt werden.
+
+In Stud.IPs LESS/CSS werden die folgenden "media query"-Intervalle verwendet:
+
+```CSS
+/* tiny: kleine Smartphones (im Hochformat) mit einer Breite von weniger als 576px. */
+
+/* small: Smartphones (im Querformat) mit einer Breite von 576px oder mehr */
+@media (min-width: 576px) { }
+
+/* medium: z.B. Tablets mit einer Breite von 768px oder mehr */
+@media (min-width: 768px) { }
+
+/* large: Desktops ab einer Breite von 1200px oder mehr */
+@media (min-width: 1200px) { }
+```
+
+Damit man diese "media queries" im LESS-Code leichter schreiben kann, gibt es dort spezielle Mixins, die einem helfen, responsive Regel zu schreiben:
+
+```CSS
+.media-breakpoint-tiny-up({ });
+.media-breakpoint-small-up({ });
+.media-breakpoint-medium-up({ });
+.media-breakpoint-large-up({ });
+
+/* Beispiel für die Nutzung: */
+
+.calhead label {
+ cursor: pointer;
+ &:hover {
+ color: @base-color-40;
+ }
+
+ .media-breakpoint-small-down({
+ .button();
+ });
+}
+
+/* oder auch: */
+.media-breakpoint-tiny-down({
+ #barTopStudip img {
+ height: 33px;
+ margin-top: 5px;
+ }
+});
+```
+
+Es können auch "media queries" verwendet werden, die in die andere Richtung (also kleiner als eine bestimmte Größe) gehen:
+
+```CSS
+/* tiny: kleine Smartphones (im Hochformat) mit einer Breite von weniger als 576px. */
+@media (max-width: 575px) { }
+
+/* small: Smartphones (im Querformat) mit einer Breite kleiner als 768px */
+@media (max-width: 767px) { }
+
+/* medium: z.B. Tablets mit einer Breite kleiner als 1200px */
+@media (max-width: 1199px) { }
+
+/* large: Desktops ab einer Breite von 1200px oder mehr */
+/* braucht man nicht, da es ja keine obere Grenze gibt */
+```
+
+Auch hier gibt es wieder LESS-Mixins:
+
+```CSS
+.media-breakpoint-tiny-down({ });
+.media-breakpoint-small-down({ });
+.media-breakpoint-medium-down({ });
+.media-breakpoint-large-down({ });
+
+
+.hidden-tiny-up
+.hidden-small-up
+.hidden-medium-up
+.hidden-large-up
+
+.hidden-tiny-down
+.hidden-small-down
+.hidden-medium-down
+.hidden-large-down
+```
+
+
+# Darstellungsvarianten
+
+Stud.IP ist eine Software, die sowohl auf verschiedenen Endgeräten/Gerätegrößen möglichst den vollständigen Funktionsumfang anzubieten versucht als auch für unterschiedliche Zielgruppen auf diesen verschiedenen Geräten möglichst gut bedienbar sein soll.
+
+**Unterstützte Geräteklassen und deren Kennzeichen:**
+
+**A. Smartphone**: Diese Geräte sind dadurch gekennzeichnet, dass
+die maximale Breite sehr schmal ist (bis zu 767 Pixel bei regulärer Auflösung),
+üblicherweise das Scrollen leicht möglich ist, die Seiten also durchaus lang werden dürfen,
+die Geräte ausschließlich per Touch, also mit dem Finger bedient werden und auf diesen Geräten keine komplexen Inhalte erstellt werden. Die übliche Display-Ausrichtung ist hochkant.
+
+**B. Tablet/kleine Desktopgeräte:** Diese Geräte sind dadurch gekennzeichnet, dass
+die maximale Breite begrenzt ist (bis zu. 1024 Pixel bei regulärer Auflösung),
+diese in der Regel per Touch bedient werden (Mausbedienung sollte ebenfalls möglich sein),
+auf diesen Geräten selten komplexe Inhalte erstellt werden. Die überwiegende Display-Ausrichtung ist quer, hochkant in einigen Anwendungsfällen.
+
+**C. Desktop/große Displays:** Diese Geräte sind dadurch gekennzeichnet, dass
+die Breite mehr als 1024 Pixel aufweist (und quasi unbeschränkt ist),
+diese ganz überwiegend per Maus bedient werden. Die übliche Display-Ausrichtung ist quer.
+
+Siehe hierzu auch die neuen Darstellungsstufen unter https://gitlab.studip.de/studip/studip/-/wikis/Responsive-Navigation.
+
+**Unterstützte UseCases und zugeordnete Nutzendengruppen:**
+
+Neben verschiedenen Geräteklassen gibt es zwei wichtige Nutzendengruppen mit unterschiedlichen UseCases. Zwischen den Gruppen gibt es teils fließende Übergänge und Schnittmengen bei den UseCases. Letztlich stellen beide Gruppen daher unterschiedliche Pole dar, für die es jeweils Optimierungen gibt.
+
+**1. Erstellung und Administration komplexer Inhalte**
+- Die üblichen Stud.IP-Gruppen für diesen UseCases sind **Admins** und (eingeschränkt) auch Lehrende
+- Der UseCase ist geprägt dadurch, dass über einen längeren Zeitraum Inhalte erstellt oder komplexe Inhalte bearbeitet werden
+- typische genutzte Elemente sind große Tabellen (viele Elemente, viele Spalten, viele mögliche Aktionen) und umfangreiche Inhalte bestehend aus mehreren Medien-Objekten (Fließtext, Film, inaktive Elemente) die zudem in sich gegliedert sind (zB. durch ein Inhaltsverzeichnis oder Überschriften)
+- Die vollständige Bedienung (insbesondere Navigation) des Systems und Nutzung von Kommunikationsfunktionen wird weiterhin erwartet und bleibt möglich
+- Funktionen/Systembereiche können gewechselt, Aktionen der Sidebar ausgeführt und Kommunikationsfunktionen können aufgerufen werden
+- Wichtige Anforderungen: Möglichst viel Platz für die zu bearbeitenden Elemente bei gleichzeitig noch möglicher Navigation
+
+**2. Konsum und Interaktion mit Inhalten ohne diese zu verändern („Lernen“)**
+- Die üblichen Stud.IP-Rechtestufen dieser Gruppe sind **Studierende** und (eingeschränkt) auch Lehrende
+- Der UseCase ist geprägt dadurch, dass über einen längerer Zeitraum Inhalte rezipiert (Texte gelesen, Filme geschaut) werden
+- typische Elemente sind umfangreiche Fließtexte, Medienobjekte (Audio oder Video) und interaktive Elemente (Fragen, Quizzes, Prüfungen)
+- Die vollständige Bedienung tritt in den Hintergrund, üblicherweise wird über längere Zeit der gleiche Kontext dargestellt
+- Zentrales Ziel ist: Möglichst keine (optische) Ablenkung durch Elemente des Systems, die über eine längere Zeit nicht benötigt werden (dabei auch keine Ablenkung durch Interaktionselementen, die Aufmerksamkeit binden) bei gleichzeitig möglich viel Platz für die Interaktion
+- Es bleibt kein Platz für die gemeinsame Darstellung des Contents und der Bedienelemente/Navigation
+- Zentrale Anforderung: Ausblenden aller störenden oder ablenkende Elemente und möglichst viel Platz für den Content
+
+
+**Darstellungsmodus zur Unterstützung der beiden UsesCases**
+
+
+**I. Vollbildmodus**
+
+Der reguläre Vollbildmodus steht auf allen Seiten zur Verfügung, um die Nutzungsgruppe 1 (Admins/Lehrende) bei der Erstellung und Bearbeitung durchweg zu unterstützen. Der Modus ist auf alle Geräteklassen (A, B, C) optimiert.
+
+Kennzeichen des aktivierten Vollbildmodus sind:
+
+- Die blaue Kopfzeile bleibt eingeblendet und ermöglicht Zugriff auf die vollständige Navigation („Hamburger-Menu“) in allen Geräteklassen
+- Der Browser selbst ist normal sichtbar (und damit auch alle anderen Fenster/Elemente des Betriebssystems)
+- ~~Der Footer kann eingeblendet bleiben (noch zu klären - für mich brauchen wir den nicht wegzulassen)~~ Der Footer ist ausgeblendet, alle Navigationselemente des Footers sind Teil des Hamburgermenüs
+- Um möglichst viel Platz für die Bedienung zu schaffen, wird die Sidebar über ein Einblendicon sichtbar bzw. wieder unsichtbar, im Default ausgeblendet
+- Noch zu diskutieren: Geräteklassen B und C (Tablet/Desktop) könnten entsprechend dem UseCase auch Schnellsuche und Notification zeigen. Derzeit haben wir sie bewusst weggelassen, evtl. verwässert dies den Modus jedoch
+
+Anzumerken ist, dass bei langen Seiten auf den Geräteklassen B und C beim Herunterscrollen die Resonsive Navigation durch das Einblenden des Hamburger Menüs ebenfalls verwendet wird, um ein schnelles Wechseln ohne Hochscrollen weiterhin zu ermöglichen.
+
+**II. Fokusmodus**
+
+Der UseCase 2 optimierte Fokusmodus kann in der Version 5.3 nur auf Seiten mit der neuen ContentBar (derzeit Courseware, Wiki und Material imOER-Campus) aktiviert werden, da davon ausgegangen werden kann, das dieser UseCase nur auf Seiten benutzt werden kann, auf denen aktiv Inhalte rezipiert werden. Der Fokusmodus ist insbesondere auf die Klasse B (Tablets) optimiert, da davon ausgegangen wird, dass damit das Lernen und Lesen am besten funktioniert. Aber auch auf Desktop (C) ist der Modus nutzbar, wenn ggf. weniger sinnvoll.
+
+Kennzeichen des aktivierten Fokusmodus sind:
+
+- Die blaue Kopfzeile wird ausgeblendet um sowohl maximalen Platz zu schaffen als auch die (versehentliche/aktive) Navigation zu unterbinden.
+- Der Browser-eigene Vollbildmodus wird ebenfalls aktiviert, da davon auszugehen ist, das auch keinerlei Ablenkungen anderer Tabs oder Bedienelemente bzw. versehentliches Antippen (auf Touchgeräten, Klasse A und B) zu verhindern. Alle anderen Fenster/Elemente des Betriebssystems werden damit (soweit wir möglich) ausgeblendet.
+- Die Sidebar wird ausgeblendet und kann nicht aktiviert werden
+- Der Footer ist nicht sichtbar
+- Die einzigen verbleibenden Bedienelemente außerhalb des Contents sind alle Elemente, die eine Bedienung innerhalb des Contents des gewählten Kontextes ermöglichen (zB. Inhaltsverzeichnis)
diff --git a/docs/docs/quickstart/sidebar.md b/docs/docs/quickstart/sidebar.md
new file mode 100644
index 0000000..c8864ce
--- /dev/null
+++ b/docs/docs/quickstart/sidebar.md
@@ -0,0 +1,79 @@
+---
+id: sidebar
+title: Zugriff auf die Sidebar
+sidebar_label: Sidebar
+---
+
+Eine Sidebar wird anhand von Widgets aufgebaut, welche kleine, voneinander unabhängige Bausteine der Seitenleiste darstellen. Eine Sidebar ist ein Singleton, sodass die einzige Instanz der Sidebar-Klasse nur über die statische Methode `Get()` erreicht werden kann:
+
+```php
+<?php
+$sidebar = Sidebar::Get();
+```
+
+
+#### Setzen des Titels der Sidebar
+
+Dazu wird die Methode `setTitle()` verwendet:
+```php
+<?php
+Sidebar::Get()->setTitle('Hallo Sidebar!');
+```
+
+Der Titel kann mittels der Methode `removeTitle()` wieder entfernt werden.
+
+
+#### Setzen eines Bildes für die Sidebar
+
+Im oberen Bereich der Sidebar ist Platz für eine Grafik. Um festzulegen, welche Grafik erscheint, wird der Pfad zur Grafik im assets-Ordner der Methode `setImage` übergeben:
+```php
+<?php
+Sidebar::Get()->setImage('some/image');
+```
+
+Die Grafik kann mittels der Methode `removeImage()` wieder entfernt werden.
+
+
+#### Setzen eines Avatars für den Kontext
+
+Im Kopf der Sidebar gibt es die Möglichkeit, zusätzlich einen Avatar anzuzeigen, der zu dem angezeigten Kontext gehört. Dies geschieht über die Methode `setContextAvatar()`, der ein Objekt vom Typ `Avatar` übergeben wird:
+```php
+<?php
+Sidebar::Get()->setContextAvatar(Avatar::getAvatar(User::findCurrent()->id));
+```
+
+Dieser Avatar kann mittels der Methode `removeContextAvatar()` wieder entfernt werden.
+
+
+#### Hinzufügen von Widgets
+
+Die Methode `addWidget()` der Klasse `WidgetContainer`, von der die Klasse Sidebar abgeleitet ist, kümmert sich um das Hinzufügen von Widgets. Ihr erster Parameter ist ein Objekt der Widget-Klasse, der optionale zweite Parameter gibt dem Widget einen Namen. Ist dieser nicht gesetzt, so wird der Klassenname des Widgets ohne das Wort Widget als Name benutzt.
+
+```php
+<?php
+
+$widget = new SearchWidget();
+Sidebar::Get()->addWidget($widget, 'search1');
+```
+
+
+#### Hinzufügen eines Widgets an einer bestimmten Position
+
+`insertWidget()` (ebenfalls aus der Klasse `WidgetContainer`) erlaubt es, ein Widget hinzuzufügen und dabei auch anhand des Namens eines anderen Widgets festzulegen, an welcher Position das Widget hinzugefügt werden soll. Der erste Parameter ist ein Objekt der Widget-Klasse, der zweite Parameter gibt an, vor welchem anderen Widget (identifiziert durch dessen Name) das neue Widget hinzugefügt werden soll. Der letzte Parameter ist wieder optional und legt den Namen des neuen Widgets fest.
+
+```php
+<?php
+
+$widget1 = new SearchWidget();
+$widget2 = new SearchWidget();
+Sidebar::Get()->addWidget($widget1, 'search1');
+Sidebar::Get()->insertWidget($widget2, 'search1', 'search2'); //widget2 (mit Namen search2) wird vor widget1 (mit Namen search1) platziert.
+```
+
+
+
+### JavaScript-Funktionen
+
+Das Mitscrollen der Sidebar kann über JS gesteuert werden. Dazu gibt es folgende Funktion:
+
+* `STUDIP.Sidebar.setSticky(bool is_sticky = true)`
diff --git a/docs/docs/quickstart/simpleormap.md b/docs/docs/quickstart/simpleormap.md
new file mode 100644
index 0000000..59c45e5
--- /dev/null
+++ b/docs/docs/quickstart/simpleormap.md
@@ -0,0 +1,315 @@
+---
+title: SimpleORMap
+---
+
+Die Klasse SimpleORMap (häufig mit SORM abgekürzt) bietet seit Stud.IP Version 1.4 einfaches objekt-relationales Mapping nach dem Active Record Muster. Mit ihrer Hilfe kann die Verwendung von SQL-Code stark reduziert werden, sodass es für Controller in den meisten Fällen nicht mehr relevant ist, wie der Name der Datenbanktabelle ist, in der die Daten liegen.
+
+
+### Allgemeines
+
+Jede von SimpleORMap abgeleitete Klasse gehört zu einer Datenbanktabelle. Eine Instanz der Klasse entspricht dann einem Datensatz der entsprechenden Tabelle, und ermöglicht damit normale Lese- und Schreiboperationen (auch "CRUD Operationen" genannt) auf der Datenbank. Alle Spalten der Tabelle werden zu virtuellen Attributen der Klasse. Die Klasse erfüllt das ArrayAccess Interface, womit der Zugriff auf die Attribute wie auf ein Array geschehen kann. Groß/Kleinschreibung der Attribute muss nicht berücksichtigt werden.
+
+#### Die SimpleORMap-Klasse
+
+Die SimpleORMap-Klasse ist die Basisklasse, welche einige Funktionen zum einfachen Lesen von Objekten aus Datenbanktabellen mitliefert.
+
+#### Die SimpleORMapCollection-Klasse
+
+Die SimpleORMapCollection-Klasse verwaltet eine Sammlung von SimpleORMap-Objekten. Sie wird beim Holen mehrerer Objekten aus der Datenbank eingesetzt und kann wie ein normales Array behandelt werden, da sie (über ein paar andere Ableitungen) die Klasse ArrayAccess implementiert.
+
+Zusätzlich hat sie ein paar Methoden, die ein Array nicht hat und die Verarbeitung von SimpleORMap-Objekten und deren Attributen erleichert. Die wichtigsten davon werden im Folgenden vorgetellt.
+
+##### pluck() - Wert eines Attributes aller Objekte mit der Methode finden
+
+Will man beispielsweise nur die Benutzer-ID aller Teilnehmer einer Veranstaltung finden, so kann man diese mittels der pluck()-Methode der SimpleORMapCollection-Klasse finden:
+
+```php
+$memberIds = Course::find($id)->members->pluck('user_id');
+```
+
+`$memberIds` enthält ein Array mit allen Benutzer-IDs.
+
+##### filter() - Objekte filtern
+
+Die filter()-Methode erlaubt es, Objekte in einer SimpleORMapCollection nach selbst gewählten Kriterien zu filtern, wobei sie wiederrum ein SimpleORMapcollection-Objekt zurückliefert. Dazu benöigt sie eine Callback-Funktion, welche bei jedem Objekt entscheidet, ob das Filterkriterium erfüllt ist, oder nicht. Liefert diese Funktion false zurück, ist das Kriterium nicht erfüllt. Ist es erfüllt, wird hingegen true zurückgeliefert.
+
+Beispiel: Finde alle Benutzer-IDs aller Dozenten einer Veranstaltung:
+
+```php
+$dozenten_ids = Course::find($seminar_id)->members->filter(function ($m) {
+ return $m['status'] === 'dozent';
+})->pluck('user_id');
+```
+
+`$m` ist ein Objekt von CourseMember, wodurch mit `$m['status']` abgefragt werden kann, ob das Mitglied Dozent in der Veranstaltung ist oder nicht.
+
+##### weitere Methoden
+
+Es gibt noch einige weitere Methoden von SimpleORMapCollection, die hier nur kurz beschrieben werden:
+
+* **map**: Verändert alle Elemente des SimpleORMapCollection-Objektes anhand einer Funktion, die ein Element annimmt und etwas beliebiges anderes zurück gibt. Das Ergebnis von `map` ist ein Array und kein SimpleORMapCollection-Objekt, weil in einem SimpleORMapCollection-Objekt nur Elemente von SimpleORMap auftauchen dürfen.
+* **toGroupedArray**: liefert ein Array der Elemente zurück, wobei die Schlüssel des Arrays gleich der IDs der Elemente ist. Das ist praktisch, um schnell innerhalb der Menge das Element mit der einer bestimmten ID zu bekommen.
+* **first**: Gibt nur das erste Element des SimpleORMapCollection-Objektes zurück.
+* **last**: Gibt nur das letzte Element des SimpleORMapCollection-Objektes zurück.
+* **val**: Gibt vom ersten Element den Wert eines bestimmten Attributes wieder. Beispielsweise gibt `Course::find($id)->members->val('status');` den Status des ersten Teilnehmers in der SimpleORMapCollection zurück.
+
+
+### Erstellen einer SimpleORMap-Klasse für eine Datenbanktabelle
+
+Eine Klasse für eine Datenbanktabelle erweitert die Klasse SimpleORMap. Es ist wichtig, den Namen der Datenbanktabelle zu setzen. Dies geschieht in der statischen Methode configure(), welcher man ein Array mit Konfigurationsparametern übergeben kann, sofern diese benötigt werden.
+```php
+class HalloWelt extends SimpleORMap
+{
+ protected static function configure ($config = [])
+ {
+ $config['db_table'] = 'hallo_welt';
+ parent::configure($config);
+ }
+}
+```
+
+Die Tabellenspalten werden automatisch aus der Datenbank ausgelesen, genau so wie der Primärschlüssel. Die Metadaten werden im Stud.IP Cache zwischengespeichert, daher muss dieser nach Tabellenänderungen auch geleert werden. Man kann diese Daten über die Methode SimpleORMap::getTableMetadata() bekommen. Der Primärschlüssel eines Datensatzes lässt sich immer über die Methode getId() auslesen, außerdem wird er auf eine virtuelle Eigenschaft id abgebildet. D.h. wenn eine Tabelle eine Spalte id besitzt, sollte sie auch der Primärschlüssel sein.
+
+#### Dokumentation im Quellcode
+
+Es ist bei SimpleORMap-Klassen üblich, im Quellcode eine Dokumentation anzulegen, die die verwendbaren Attribute beschreibt. Dies erleichtert es anderen Entwicklern, die Klasse zu verwenden, da kein Blick in das Datenbankschema geworfen werden muss, um rauszufinden, welche Attribute verfügbar sind.
+
+Zur Dokumentation (als Beispiel mit der obigen HalloWelt-Klasse) wird oberhalb der Klassendefinition ein Block mit folgendem Schema eingefügt:
+```php
+/**
+ * @property int id database column
+ * @property string user_id database column
+ * @property string greeting database column
+**/
+```
+
+### Objekte aus der Datenbank lesen
+
+#### anhand des Primärschlüssels
+
+##### einzelnes Objekt holen
+
+Zum Finden eines vorhandenen Datensatzes anhand des Primärschlüssels benutzt man die find() Methode. Übergibt man dem Konstruktor einen Primärschlüssel, so wird ein vorhandener Datensatz mit diesem Primärschlüssel geladen. Wurde der Datensatz nicht gefunden, wird null zurückgegeben.
+
+```php
+$id = 1;
+
+$course = Course::find($id);
+if ($course) {
+ echo $course->name;
+}
+```
+
+##### viele Objekte holen
+
+Benötigt man eine Menge von Objekten, die man anhand einer Liste von Primärschlüsseln ermittelt hat, kann man die Methode findMany() benutzen. Diese nimmt ein Array mit Schlüsseln entgegen und als zweiten Parameter optional einen ORDER BY Teil.
+
+```php
+$courses = Course::findMany($course_ids, "ORDER BY name");
+```
+
+##### viele Objekte holen und direkt weiterverarbeiten
+
+Möchte man dagegen eine Menge von Objekten nicht erzeugen, sondern prozessieren, gibt es die genannten Methoden noch in einer findEach... und findAndMap... Ausprägung. Diese Methoden fordern als ersten Parameter ein "callable", und sie iterieren durch die gefundenen Datensätze und rufen jeweils das callable mit einem Objekt auf. FindEach... gibt die Anzahl der iterierten Objekte zurück, findAndMap.. dagegen ein Array mit den Rückgabewerten des callable.
+
+```php
+//erzeugt ein Array mit Veranstaltungstiteln
+$courses = Course::findAndMapMany(function ($course) {
+ return $course->getFullname('number-name-semester');
+}, $course_ids, "ORDER BY name");
+```
+
+
+#### anhand von SQL-Anweisungen
+
+Jedes SimpleORMap Objekt verfügt aufgrund der Vererbung von SimpleORMap über eine ganze Reihe von findBy-Methoden. Die wichtigste davon ist findBySQL(), da man mit dieser Methode den Teil einer SQL-Abfrage übergeben kann, welche rechts neben dem WHERE-Teil der SQL-Abfrage stehen soll. Der zweite Parameter der findBySQL()-Methode besteht aus einem assoziativen Array, welches Parameter enthält, die in die Abfrage eingebaut werden sollen. Die Rückgabe ist immer ein Array (genauer gesagt eine SimpleORMapCollection) von SimpleORMap Objekten der entsprechenden Klasse.
+
+```php
+$courses = Course::findBySQL("name LIKE ? ORDER BY name", [$search]);
+```
+
+##### Finden einzelner Objekte anhand von SQL-Anweisungen
+
+Wenn man nur ein Objekt durch die Abfrage holen möchte, kann man stattdessen findOneBySQL() verwenden. Hier wird grundsätzlich nur das erste Element der Abfrage als Objekt erzeugt und zurückgegeben.
+```php
+$newest_course = Course::findOneBySQL("1 ORDER BY mkdate");
+```
+
+##### Finden von Objekten anhand dessen Attributen
+
+Ein SimpleORMap-Objekt besitzt automatisch auch findBy-Methoden für Abfragen nach allen Attributen (Datenbankspalten), die definiert wurden, sodass Abfragen der folgenden Art möglich sind:
+
+```php
+$courses = Course::findManyByStatus([1,4,5,7], "ORDER BY status,name");
+
+$courses = Course::findByStatus(1, "ORDER BY status,name");
+
+$course = Course::findOneByStatus(1, "ORDER BY mkdate");
+```
+
+Analog können ab Stud.IP 4.2 Einträge anhand eines Attributwerts gezählt bzw gelöscht werden:
+
+```php
+// Zähle alle versteckten Veranstaltungen im System
+$hidden_courses = Course::countByVisible(0);
+
+// Lösche alle Veranstaltungen mit der Veranstaltungsnummer "TODO"
+Course::deleteByVeranstaltungsnummer('TODO');
+```
+
+### Bearbeitung eines Objektes
+
+Nach dem Laden eines Objektes aus der Datenbank kann dieses anhand der Attribute geändert werden. Dazu setzt man einfach die Attribute auf andere Werte:
+```php
+$course = Course::find($id); //laden
+$course->name = 'Neue Veranstaltung'; //ändern
+$course->store(); //speichern
+```
+
+#### Speichern
+
+Um die geänderten Werte eines Objektes in der Datenbank zu speichern ruft man dessen store()-Methode auf. Es findet keine automatische Speicherung statt, sodass Änderungen, welche nicht mittels store() in die Datenbank überführt wurden, verloren gehen.
+
+store() liefert eine Zahl zurück, die die Anzahl der geänderten Datensätze anzeigt (da u.U. Relationen gespeichert werden, kann das auch > 1 sein). Es kann auch false zurück geliefert werden, das bedeutet dann, das die Speicherung unterbrochen wurde, z.B. wegen eines Fehlers oder eines callbacks (`before_store`, `before_update`), der die Speicherung verhindert hat.
+
+
+#### Änderungen vor dem Speichern erkennen
+
+Möchte man überprüfen, ob sich das Objekt seit dem letzten Lesen Änderungen enthält, kann man die Methode isDirty() aufrufen. Analog dazu kann man ein einzelnes Attribut auf Änderung mit isFieldDirty($field) überprüfen.
+
+
+#### Zurücknehmen von Änderungen
+
+Eine Änderung kann mit revertValue() zurückgenommen werden. Den ursprünglichen Wert kann man, sofern vorhanden mit getPristineValue() herausbekommen.
+
+
+#### Beispiel zum Speichern:
+
+Im Folgenden wird ein Course-Objekt (welches eine Veranstaltung wiederspiegelt) geladen, geändert, auf Änderungen geprüft, die Änderungen zurückgenommen und gespeichert.
+
+```php
+$course = Course::find($id);
+$course->name; // "Alte Veranstaltung";
+$course->name = 'Neue Veranstaltung';
+$course->isDirty(); // ist true
+$course->isFieldDirty('number'); // ist false
+$course->getPristineValue('name'); // liefert "Alte Veranstaltung"
+$course->revertValue('name'); //Zurücknehmen der Änderungen
+$course->store(); //ergibt 0, da keine Veränderungen mehr vorliegen (Änderungen wurden ja zurückgenommen)
+```
+
+
+### Anlegen eines Objektes
+
+Um einen neuen Datensatz zu erzeugen, erstellt man ein neues Objekt, weist zwingen benötigte Werte über dessen Attribute zu und ruft die store() Methode auf:
+
+```php
+$course = new Course();
+$course->name = 'Neue Veranstaltung';
+$course->store();
+```
+
+Da in diesem Beispiel kein Wert für den Primärschlüssel gesetzt wurde, wird vor dem store() implizit ein neuer Schlüssel erzeugt. Es wird bei einwertigem Schlüssel davon ausgegangen, dass ein für Stud.IP typischer 32 Zeichen langer Schlüssel benutzt wird. Wenn der Schlüssel in der Datenbank auf AUTO_INCREMENT gesetzt ist, wird stattdessen nach dem store() der automatisch von der Datenbank vergebene Schlüssel geladen. Man kann dieses Verhalten auch modifizieren (siehe #callbacks)
+
+### Ein Objekt löschen
+
+Um ein Objekt zu löschen ruft man die delete() Methode auf, nachdem man es aus der Datenbank geladen hat. delete() liefert die Anzahl der gelöschten Datensätze zurück. Hier kann es ebenfalls vorkommen, dass die Anzahl größer als 1 ist, wenn kaskadierend abhängige Objekte mit gelöscht wurden. Es kann auch false zurück geliefert werden, was bedeutet, dass das Löschen unterbrochen wurde, was aufgrund eines Fehlers oder eines callbacks (`before_delete`) passiert sein kann.
+
+Das Objekt selbst wird nach dem Aufruf von delete() nicht automatisch gelöscht, aber geleert. Ob man ein gelöschtes Objekt vor sich hat, kann man mit dessen Methode isDeleted() nachprüfen.
+
+```php
+$course = Course::find($id); //Objekt laden
+$course->delete(); //Objekt löschen
+$course->getId(); // ergibt null
+$course->isNew(); // ergibt false;
+$course->isDeleted(); //ergibt true
+```
+
+
+### Relationen
+
+Seit der Version 2.4 von Stud.IP kann SimpleORMap auch Relationen zwischen den Datenbanktabellen beziehungsweise deren beinhalteten Objekten abbilden. Das Grundprinzip ist, dass ein Objekt einer von SimpleORMap abgeleiteten Klasse ein weiteres Attribut hat, um auf eine andere Datenbanktabelle zuzugreifen. Beispielsweise sind einem Kursobjekt mehrere User-Objekte zugeordnet, die als Teilnehmer der Veranstaltung gelten. Zwischen den Tabellen `seminare` und `auth_user_md5/user_info` gibt es eine Relationstabelle `seminar_user`. Es gibt also eine n:m Verknüpfung, die in der Course-Klasse abgebildet wurde, um auf einfache Art und Weise über ein Attribut eines Course-Objektes die Teilnehmer zu bekommen:
+
+```php
+$course = new Course($seminar_id);
+$courseMembers = $course->members;
+```
+
+**WICHTIG:** Dieses Beispiel zeigt einen Fallstrick bei der Benutzung von Relationen in SimpleORMap auf: Im Beispiel repräsentiert $courseMembers nicht die User, sondern Objekte der SimpleORMap-Klasse CourseMember. Damit ist die Variable `$coursemembers` zwar korrekt befüllt, weil man damit auch auf die Felder der Tabelle `seminar_user` leicht zugreifen kann, aber meistens interessiert man sich nicht für die Relationstabelle, sondern für die verknüpften Objekte wie hier die zugehörigen User-Objekte. Der naheliegende Weg, um an diese Objekte zu kommen, bestünde darin, alle Objekte von `$courseMembers` zu durchlaufen und sich dann die dazugehörigen User-Objekte zu holen. Dank SimpleORMapCollection gibt es einen Weg, bei dem weniger Code geschrieben werden muss.
+
+
+Um zum Beispiel alle User-Objekte aller Dozenten einer Veranstaltung zu bekommen, werden aus allen Veranstaltungsteilnehmern diejenigen gefiltert, welche den Status Dozent haben und nur deren Benutzer-IDs gespeichert. Über die statische Methode findMany() der User-Klasse können dann alle User-Objekte der Dozenten der gewählten Veranstaltung ausgelesen werden.
+
+```php
+$dozenten_ids = Course::find($seminar_id)->members->filter(function ($m) {
+ return $m['status'] === 'dozent';
+})->pluck('user_id');
+$dozenten = User::findMany($dozenten_ids, "ORDER BY Nachname, Vorname");
+```
+
+
+Der Vorteil bei diesem Vorgehen ist, dass nicht so viele Datenbankabfragen getätigt werden, als wenn alle Benutzer-Objekte einzeln geladen würden. Denn findMany() führt nur eine einzige SQL-Abfrage aus, deren Ergebnisset dann automatisch zu Objekten vom Typ User wird.
+
+### Definition von Relationen
+
+
+| Variable | Beschreibunt |
+| ---- | ---- |
+|assoc_foreign_key |Definiert die Spalte der zweiten Tabelle, mit der der Key (Default Primary Key) des Grundobjekts verglichen wird |
+
+#### Beispiel zur Definition von Relationen
+
+Mit der SimpleORMap ist es sehr einfach, eine Baumstruktur abzubilden, wie das folgende Beispiel zeigt. Im Beispiel wird vorausgesetzt, dass die verwendete Tabelle sample_table eine Spalte id besitzt, welche den Primärschlüssel darstellt. Ebenso wird vorausgesetzt, dass eine Spalte parent_id existiert, welche das jeweilige Elternelement eines Tabelleneintrags referenziert.
+
+```php
+class SampleSorm extends SimpleORMap
+{
+ protected static function configure($config = [])
+ {
+ $config['db_table'] = 'sample_table';
+
+ // Kindverknüpfung definieren
+ $config['has_many']['children'] = [
+ 'class_name' => SampleSorm::class,
+ 'assoc_func' => 'findByParent_id'
+ ];
+
+ // Elternverknüpfung definieren
+ $config['belongs_to']['parent'] = [
+ 'class_name' => SampleSorm::class,
+ 'foreign_key' => 'parent_id'
+ ];
+ parent::configure($config);
+ }
+}
+```
+
+Für jedes Objekt der Klasse SampleSorm können nun dessen Kindelemente und dessen Elternelement als SimpleORMap-Objekte direkt erreicht werden.
+
+### Joins
+
+Auch Joins sind mit SimpleORMap möglich. Diese können den Schreibaufwand stark verringern, wenn die Auswahlkriterien für Objekte anhand anderer Tabellen festgelegt werden sollen.
+
+Zur Benutzung eines Joins wird einfach die statische Methode findBySql() derjenigen SimpleORMap-Klasse aufgerufen, welche den gewünschten Objekttyp abbildet. Das Ergebnis liegt dann im gewünschten Objekttyp vor.
+
+Im Gegensatz zu den bisher vorgestellten Aufrufen von findBySql() muss bei der Verwendung von Joins der SQL-Code ab der JOIN-Anweisung angegeben werden. Dies bedeutet, dass nur der Teil "SELECT FROM tabellenname" der SQL-Anweisung von der SimpleORMap-Klasse generiert wird. Der Rest der Anweisung muss manuell geschrieben werden.
+
+#### Beispiel
+Im folgenden Beispiel werden alle Veranstaltungstermine eines Teilnehmers abgerufen:
+
+```php
+<?php
+$courseDates = CourseDate::findBySQL(
+ "LEFT JOIN seminar_user "
+ . "ON (seminar_user.Seminar_id = termine.range_id ) "
+ . "WHERE (seminar_user.user_id = :user_id )",
+ ['user_id' => $user_id]
+);
+```
+
+
+### Weitere Dokumente
+
+* [Präsentationsfolien SORM 1](/pdf/entwicklerworkshop2013-activerecord.pdf)
+* [Präsentationsfolien SORM 2](/pdf/entwicklerworkshop2014-attack_of_the_sorm.pdf)
+* [Präsentationsfolien SORM 3](/pdf/entwicklerworkshop2015-sorm_sucks.pdf)
diff --git a/docs/docs/quickstart/studip-pdo.md b/docs/docs/quickstart/studip-pdo.md
new file mode 100644
index 0000000..cc9055b
--- /dev/null
+++ b/docs/docs/quickstart/studip-pdo.md
@@ -0,0 +1,86 @@
+# StudipPDO
+
+Stud.IP benutzt für die Datenbankzugriffe eigene, von `PDO` und `PDOStatement` abgeleitete Klassen. `DBManager::get()` liefert immer automatisch eine Instanz von `StudipPDO` zurück. Man kann dieses Objekt genauso benutzen wie ein Standard-PDO Objekt, darüberhinaus enthält es aber noch ein paar Erweiterungen, die den Umgang mit der Datenbank erleichtern.
+
+[StudipPDO](https://gitlab.studip.de/studip/studip/-/blob/main/lib/classes/StudipPDO.class.php)
+
+## Automatische Parametererkennung, Zusätzliche Parameter Typen
+
+Die an eine Statement übergebenen Parameter werden anhand des PHP Typs auf einen passenden PDO Parametertypen gemappt. D.h. z.B., wenn ein übergebener Parameter `NULL` in PHP ist, wird er auch in der Abfrage als ein SQL `NULL` ersetzt. Es stehen noch neue Parameter Typen zur Verfügung:
+
+### `StudipPDO::PARAM_ARRAY`
+Der Parametertyp erlaubt die Übergabe eines Arrays, das dann zu einer Wertliste expandiert wird. Zu benutzen mit dem `IN ()` Konstrukt in SQL.
+
+Beispiel:
+```php
+$st = DBManager:get()->prepare("SELECT * FROM seminare WHERE status IN(?)");
+$st->execute([[1,2,3,4]]);
+```
+
+wird ausgeführt als
+```sql
+SELECT * FROM seminare WHERE status IN (1, 2, 3, 4)
+```
+
+### `StudipPDO::PARAM_COLUMN`
+Dieser Parametertyp erlaubt die Übergabe eines Strings, der ohne weitere Behandlung in die SQL Abfrage eingesetzt wird. Damit kann z.B. ein Parameter im `ORDER BY` Teil benutzt werden. Weil das ein Sicherheitsproblem sein kann, wird in jedem Fall jedes nicht-Wort Zeichen aus dem Parameter herausgefiltert.
+
+Beispiel:
+```php
+$st = DBManager:get()->prepare("SELECT * FROM auth_user_md5 WHERE perms IN (:perms) ORDER BY :sorter");
+$st->bindValue(':status', ['tutor','dozent']);
+$st->bindValue(':sorter', 'Nachname', StudipPDO::PARAM_COLUMN);
+```
+
+wird ausgeführt als
+```sql
+SELECT * FROM auth_user_md5 WHERE perms IN ('tutor','dozent') ORDER BY Nachname
+```
+
+## Prepared Statements to go
+
+Da Prepared Statements in der Anwendung etwas umständlich sind, wurde ein Abkürzung für häufig benutzte Varianten eingebaut. Das erspart auch die Eingabe der PDO Konstanten für den fetch-mode. Dazu stehen diese fetch Methoden außerdem direkt im StudipPDO Objekt zur Verfügung stellen, mit der Möglichkeit eine Query und die Parameter direkt mit anzugeben.
+
+Beispiele:
+```php
+<?php
+$db = DBManager::get();
+
+//für DELETE UPDATE etc
+$db->execute("DELETE FROM xxx WHERE id=?", [$id]);
+
+//nur das erste Ergebnis der Abfrage als assoc array holen
+$db->fetchOne("SELECT * FROM xxx WHERE id=?", [$id]);
+
+//nur den Wert der ersten Spalte des ersten Ergebnisses der Abfrage holen
+$db->fetchColumn("SELECT id FROM xxx WHERE id=?", [$id]);
+
+//alles als array holen, erste Spalte als Schlüssel, zweite als Wert
+$db->fetchPairs("SELECT id,value FROM xxx WHERE id IN (?)", [$ids]);
+
+//alles als array holen, nur die Werte der ersten Spalte
+$db->fetchFirst("SELECT value FROM xxx WHERE id IN (?)", [$ids]);
+
+//alles als assoc array holen,
+$db->fetchAll("SELECT * FROM xxx WHERE id IN (?)", [$ids]);
+
+//alles als assoc array holen, die erste Spalte wird zum Schlüssel,
+//der Rest wird gruppiert, wenn zu einem Schlüssel mehrere Zeilen vorhanden sind,
+// wird nur die erste zurückgegeben
+$db->fetchGrouped("SELECT id, xxx.* FROM xxx WHERE id IN (?)", [$ids]);
+
+//alles als assoc array holen, die erste Spalte wird zum Schlüssel, die zweite wird
+//gruppiert und als array zurückgegeben
+//(Anm. Arne:) ersetzt: fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_GROUP)
+$db->fetchGroupedPairs("SELECT id, value FROM xxx WHERE id IN (?)", [$ids]);
+
+//dritter Parameter kann bei allen Methoden die Arrays liefern ein callable sein
+//z.B. um bei den Gruppierungen eine Aggregatfunktion zu realisieren
+$count = function ($a) {
+ return count($a);
+};
+$db->fetchGroupedPairs("SELECT id, value FROM xxx WHERE id IN (?)", [$ids], $count);
+
+//das Ergebnis wäre das gleiche wie hier
+$db->fetchPairs("SELECT id, COUNT(*) FROM xxx WHERE id IN (?) GROUP BY id", [$ids]);
+```
diff --git a/docs/docs/quickstart/templates.md b/docs/docs/quickstart/templates.md
new file mode 100644
index 0000000..7544bbf
--- /dev/null
+++ b/docs/docs/quickstart/templates.md
@@ -0,0 +1,418 @@
+---
+id: templates
+title: Templates
+sidebar_label: Templates
+---
+
+## Allgemeines
+
+Templates liegen entweder im Ordner `/templates` oder unter `/app/views`. In ersterem sind Templates für die Seitenleite und generell verwendete Templates abgelegt, in letzterem hingegen Templates, die von Trails-Controllern automatisch für bestimmte Aktionen geladen werden, sofern nicht manuell andere Templates ausgewählt werden.
+
+Stud.IP-Templates sind im allgemeinen HTML-Dateien, die mit PHP-Codeschnipseln angereichert sind, um bestimmte Dinge, die z.B. in einem Trails-Controller geladen wurden, auszugeben.
+
+### PHP-Code in Templates
+
+PHP-Code wird mit kurzen "Tags" eingeleitet. Statt `<?php` wird einfach `<?` geschrieben. Soll an der Stelle, an der der PHP-Code steht, eine Ausgabe erfolgen, so ist der öffnende Tag `<?=`. Die längere Schreibweise `<? echo $VARIABLE` sollte nicht verwendet werden.
+
+Ein PHP-Codefragment schließt mit `?>`.
+
+
+### Tabellen
+
+#### Stud.IP-Design
+
+Damit Tabellen im Stud.IP-Design erscheinen, müssen sie die Klasse `default` besitzen. Dadurch werden verschiedene Hervorhebungen automatisch zur Tabelle hinzugefügt.
+
+#### Tablesorter
+
+Um eine Tabelle clientseitig sortierbar zu machen, kann das jQuery-Plugin Tablesorter eingebunden werden. Für die Einbindung muss kein eigener JavaScript-Code geschrieben werden. Es genügt, die Klasse `sortable-table` dem `<table>`-Element hinzuzufügen und den Kopfspalten (`<th>`-Elemente) das Attribut `data-sort` hinzuzufügen, welches angibt, wie die Spalten zu sortieren sind.
+
+Die folgenden Sortiermethoden für Spalten sind in Stud.IP gebräuchlich:
+
+* `digit`: Sortierung nach Zahlenwert
+* `text`: alphabetische Sortierung
+* `time`: Sortierung nach Uhrzeit
+* `htmldata`: Sortierung nach der Angabe im Attribut `data-sort-value` an der jeweiligen Zelle
+
+Weitere Sortiermethoden sind in der Tablesorter-Dokumentation zu finden:
+
+https://mottie.github.io/tablesorter/docs/example-option-built-in-parsers.html
+
+Um eine Standard-Sortierung festzulegen, wird dem table-Element das Attribut `data-sortlist` hinzugefügt. Dieses besteht aus einem zweidimensionalen Array, wobei die zweite Dimension aus zwei Werten besteht: Die Spaltennummer (beginnend bei 0) und die Sortierung (0 = aufsteigend, 1 = absteigend).
+
+Beispiel: Hat man eine Tabelle mit 3 Spalten und möchte, dass die zweite Spalte standardmäßig absteigend sortiert werden soll, so setzt man `data-sortlist` auf den Wert `[[1, 1]]`. Soll die zweite Spalte hingegen aufsteigend sortiert werden, ist der Wert für `data-sortlist` `[[1, 0]]`.
+
+
+## Flexi-Templates
+
+### Beispiel 1: "Hello World"
+
+Wie es sich gehört, kommt als erstes Beispiel das bekannte "Hello world". Dafür brauchen wir zwei Sachen:
+
+
+* eine Template-Datei, die wie ein Lückentext funktioniert,
+* ein PHP-Skript, das diesen Lückentext füllt und ausgibt.
+
+Die Template-Datei liegt aus Hygienegründen in einem eigenen Verzeichnis `templates`. Damit sieht dann das Beispiel so aus:
+
+index.php
+templates/hello_world.php
+
+Der Lückentext ist in der Datei `templates/hello_world.php` gespeichert. Die Flexi_Template-Engine benutzt die Endung einer Template-Datei, um die Art dieser zu erkennen. Eine Endung ".php" weißt dabei auf ein `Flexi_PhpTemplate` hin. Diese Art von Template ist einfach ein plain vanilla PHP-Skript. Wie sieht also unser Template aus?
+
+```php
+<h1>Hello, <?= $name ?>!</h1>
+```
+
+Offenbar wird der Platzhalter `$name` verwendet, um den Namen des Gegrüßten anzuzeigen.
+
+Wie füllt man also dann diesen Lückentext? Schauen wir uns doch die Datei `index.php` an:
+
+```php
+<?php
+
+# load flexi lib
+require_once dirname(__FILE__) . '/../../vendor/flexi/flexi.php';
+
+# where are the templates
+$path_to_the_templates = dirname(__FILE__) . '/templates';
+
+# we need a template factory
+$factory = new Flexi_TemplateFactory($path_to_the_templates);
+
+# open template
+$template = $factory->open('hello_world');
+
+# set name of the greetee
+$template->set_attribute('name', 'Axel');
+
+# render template
+echo $template->render();
+```
+
+Zunächst wird offenbar die Flexi-Bibliothek geladen, daraufhin eine Variable mit dem Pfad zum Verzeichnis, wo unser Template liegt, gefüllt und dann mit dieser eine `Flexi_TemplateFactory` erzeugt.
+
+Wie der Name schon andeutet, wird diese factory dazu benutzt, Templates herzustellen. Und genau das passiert als nächstes. Indem man der factory die Nachricht #open schickt, erhält man ein Template-Objekt. Dazu muss man als Argument nur den Namen der Template-Datei mitgeben. Da die factory ein wenig schlau ist, reicht ihr dabei der Name der Datei ohne Endung (die man aber natürlich auch mitangeben dürfte; man muss es eben nur nicht..). Für unser Template `hello_world.php` genügt also ein "hello_world".
+
+Die Verwendung des Namens ohne Endung erlaubt ein Feature, auf das wir später noch einmal eingehen werden.
+
+Zurück zum Thema: In einem weiteren Schritt setzen wir für das Template das Attribut "name" auf den Wert "Axel". Und zum Schluss lassen wir das Template auswerten und geben das Ergebnis (einen String) aus.
+
+Wenig überraschend erhält man nach Ausführung im Browser:
+
+![image](../assets/hello_axel.png)
+
+### Beispiel 2: "Darf's ein bisschen mehr sein?"
+
+Eben haben wir gerade eine "Lücke" mit einem String gefüllt. Als nächstes probieren wir das mal mit anderen Sachen als Strings.
+
+Schauen wir uns als erstes einmal das PHP-Skript an, das unser Template füllen wird.
+
+```php
+<?php
+
+# load flexi lib
+require_once dirname(__FILE__) . '/../../vendor/flexi/flexi.php';
+
+# where are the templates
+$path_to_the_templates = dirname(__FILE__) . '/templates';
+
+# we need a template factory
+$factory = new Flexi_TemplateFactory($path_to_the_templates);
+
+# open template
+$template = $factory->open('quotes');
+
+
+# set quotes
+$quotes = [
+ [
+ 'author' => 'August Strindberg',
+ 'quote' => 'Der Mensch ist ein wunderliches Tier.'
+ ],
+ [
+ 'author' => 'Pierre Reverdy',
+ 'quote' => 'Der Mensch ist ein Tier, das sich selbst gezähmt hat.'
+ ],
+ [
+ 'author' => 'Thomas Niederreuther',
+ 'quote' => 'Der Mensch ist das einzige Tier, das sich für einen Menschen hält.'
+ ],
+ [
+ 'author' => 'Durs Grünbein',
+ 'quote' => 'Der Mensch ist das Tier, das Kaugummi kaut.'
+ ],
+ [
+ 'author' => 'Mark Twain',
+ 'quote' => 'Der Mensch ist das einzige Tier, das erröten kann - oder sollte.'
+ ]
+];
+
+# select one randomly
+shuffle($quotes);
+$quote_of_the_day = array_shift($quotes);
+
+$template->set_attributes([
+ 'quotes' => $quotes,
+ 'quote_of_the_day' => $quote_of_the_day
+]);
+
+
+# set current time
+$time = time();
+$template->set_attribute('time', $time);
+
+
+# render template
+echo $template->render();
+```
+
+Hier erwartet uns nichts besonderes. Die Zeilen 1-13 sind dieselben, die wir schon im ersten Beispiel hatten. Die Flexi-Bibliothek wird geladen, der Pfad zu den Templates wird gesetzt und mit ihm eine Template-Factory erzeugt, die wir dann verwenden, um das Template namens "quotes" zu öffnen.
+
+Danach (Zeilen 16-27) wird ein Vektor von Zitaten angelegt, um dann in den Zeilen 29-31 diesen Vektor zu mischen und den ersten zum Zitat des Tages zu kören.
+
+Interessanter wird es dann in Zeile 33. Dort senden wir an unser Template eine neue Nachricht #set_attributes. Anstatt also zweimal hintereinander #set_attribute aufzurufen, was so aussehen würde:
+
+```php
+template->set_attribute('quotes', $quotes);
+template->set_attribute('quote_of_the_day', $quote_of_the_day);
+```
+
+setzen wir stattdessen direkt ein Array von Schlüssel-Wert-Paaren.
+
+Die Zeilen 37-39 demonstrieren, dass man hinterher gerne weitere Attribute wie gewohnt setzen kann. Auch andersherum wäre das kein Problem gewesen (also zunächst #set_attribute und dann #set_attributes). Die Nachricht #set_attributes überschreibt nämlich nur, löscht also nicht alle vorher eingetragenen Attribute.
+
+Die Zeilen 42-43 sollten einem dann wieder bekannt vorkommen. Wir evaluieren die Attribute im Kontext der Template-Datei und geben diese aus.
+
+
+Die Template-Datei ist wieder ein Flexi_PhpTemplate also ein gewöhnliches PHP-Skript. Diesmal haben wir aber eine kleine Überraschung eingebaut. Es soll nämlich ein bisschen Ausgabelogik demonstriert werden:
+
+```php
+<h1>Zitat des Tages (<?= date('d.m.Y', $time) ?>)</h1>
+<p>
+ <em>
+ &#8222;<?= $quote_of_the_day['quote'] ?>&#8220;
+ </em>
+ (<?= $quote_of_the_day['author'] ?>)
+</p>
+
+
+<? if (sizeof($quotes)) : ?>
+ <h1>Mehr Zitate</h1>
+ <? foreach ($quotes as $quote) : ?>
+ <p>
+ <em>
+ &#8222;<?= $quote['quote'] ?>&#8220;
+ </em>
+ (<?= $quote['author'] ?>)
+ </p>
+ <? endforeach ?>
+<? endif ?>
+```
+
+Interessant sind hier wohl die Aufrufe von Ausgabefunktionen wie #date in Zeile 1, die Verwendung eines `if`-Konstrukts in Zeile 10 (und natürlich dessen Ende in Zeile 20) und die Verwendung von `foreach` in Zeile 12 (und 19).
+
+Wenn man also von der Verwendung der alternativen Syntax für `if` und `foreach` (http://de.php.net/manual/en/control-structures.alternative-syntax.php) absieht, sollte der Inhalt des Templates für einen wahren PHP-Connoisseur absolut Standardcode sein.
+
+Eine Beispielausgabe sieht dann also ungefähr so aus:
+
+![image](../assets/quotes.png)
+
+### Beispiel 3: "Und nun in hübsch"
+
+Wenn man viele verschiedene Templates fertig gestellt hat, fällt einem irgendwann auf, dass sich darin zu Beginn und zum Ende eines Templates Text wiederholt. Nicht selten liegt dann ungefähr folgender Aufbau in den Templates vor:
+
+```php
+<!-- hier steht ein "header" -->
+
+<!-- dann folgt der inhalt -->
+Hello World
+
+<!-- und zum schluss noch ein footer -->
+```
+
+Da Header und Footer in allen Dateien gleich ist, liegt es nahe, sich nicht immer zu wiederholen (DRY - Don't Repeat Yourself). Für die Flexi-Templates gibt es speziell zu diesem Zweck einen Mechanismus: "Layouts".
+
+Im folgenden wird nun die Zitatensammlung aus Beispiel 2 in ein Layout eingebettet.
+
+Zunächst aber die theoretische Seite: Layouts sind ein Beispiel für Martin Fowlers "Decorator Pattern". Templates werden dazu in andere Layout-Templates eingebettet. Diese Layout-Templates bilden eine gemeinsame Struktur für die eingebetteten Inhalts-Templates. Das Layout-Template entscheidet, wohin der Inhalt der eingebetteten Templates eingefügt wird.
+
+![image](../assets/layout.png)
+
+Layout-Templates sind dabei ganz normale Templates, allerdings mit zwei zusätzlichen Eigenschaften:
+
+* Die Ausgabe des Inhalttemplates wird im Attribut "content_for_layout" zur Verfügung gestellt.
+* Alle Attribute, die das Inhalts-Template erhält, ebenso wie alle Variablen, die das Inhalts-Template während seiner Evaluierung setzt, werden an das Layout-Template vererbt.
+
+Ein Layout-Template sieht dann also im Prinzip so aus:
+
+```php
+header
+<?= $content_for_layout ?>
+footer
+```
+
+Wenn dann das Inhalt-Template folgende Ausgabe erzeugt:
+
+`Hello, world!`
+
+wäre das Endergebnis dann:
+
+```shell
+header
+Hello, world!
+footer
+```
+
+Um also nun einem Template ein Layout zuzuordnen, ruft man einfach die Methode #set_layout auf:
+
+```php
+$template->set_layout('my_chunky_layout');
+```
+
+
+Zurück zur Zitatensammlung aus Beispiel 2. Um unsere Zitate in ein Layout einzubetten, müssen wir lediglich:
+
+* dem Template-Objekt die Nachricht #set_layout senden
+* ein Layout-Template erstellen
+
+Der erste Punk ist schnell erledigt. In dem PHP-Skript, das in Beispiel 2 unser Template-Objekt erzeugt hat, fügen wir folgende Zeile hinzu:
+
+
+```php
+[..]
+# open template
+$template = $factory->open('quotes');
+
+
+# set layout
+$template->set_layout('layout');
+
+
+# set quotes
+[..]
+```
+
+Damit bleibt nur noch Punkt 2: ein Layout-Template namens 'layout' erstellen:
+
+```php
+<html>
+<head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <title><?= $title ?></title>
+ <link rel="stylesheet" type="text/css" href="style.css" media="screen"/>
+</head>
+<body>
+ <?= $content_for_layout ?>
+</body>
+</html>
+```
+
+Das Stylesheet wird an dieser Stelle nicht wiedergegeben. Wichtig ist ja auch nur Zeile 8, in der die Ausgabe des Inhalts-Templates eingefügt wird.
+
+Zusätzlich wird nun noch der Einsatz von in Inhalt-Templates gesetzten Variablen demonstriert. Gegenüber dem in Beispiel 2 verwendeten Template 'quotes', enthält dieses nun ausserdem folgende Zeile:
+
+```php
+[..]
+<? $title = "Zitate"; ?>
+[..]
+```
+
+Da nun im Inhalts-Template die Variable 'title' gesetzt wurde, kann diese im Layout-Template verwendet werden. (siehe dazu Zeile 4 im Layout-Template oben)
+
+Damit sind nun alle wichtigen Mechanismen der Flexi-Templates vorgestellt worden. Es folgen nun noch ein paar Gimmicks...
+
+### Meanwhile, in another place &hellip;
+
+Bevor wir zu den Gadgets kommen, noch ein kurzer Überblick über die API, die die Flexi-Templates bietet.
+
+Zunächst kurz die Methoden, die ein Flexi_Template-Objekt bietet. Anzumerken ist, dass diese Objekte dieser Klasse nicht direkt instanziiert werden können, da man dafür eine Template-Factory benötigt.
+
+```php
+class Flexi_Template {
+
+ function get_attribute($name);
+ function get_attributes();
+
+ function set_attribute($name, $value);
+ function set_attributes($attributes);
+
+ function clear_attributes();
+ function clear_attribute($name);
+
+ function render($attributes = null, $layout = null);
+
+ function set_layout($layout);
+}
+```
+
+Die ersten sechs Methoden:
+
+* #get_attribute
+* #get_attributes
+* #set_attribute
+* #set_attributes
+* #clear_attributes
+* #clear_attribute
+
+dienen zum Setzen, Abfragen und Entfernen von Attributen. Für gewöhnlich werden wohl nur die beiden Setter
+
+* #set_attribute
+* #set_attributes
+
+benötigt. Während #set_attribute einem Schlüssel einen Wert zuordnet:
+
+```php
+$template->set_attribute('key', new Value());
+```
+
+Mit der Methode #set_attributes kann man gleich ein ganzes (assoziatives) Array von Schlüssel-Wert-Paaren setzen:
+
+```php
+$attributes = [];
+$attributes['title'] = "a title";
+$attributes['content'] = "some content";
+
+$template->set_attributes($attributes);
+```
+
+Anzumerken ist, dass diese Methode #set_attributes die bereits gesetzten Attribute nicht entfernt, sondern nur bereits vorhandene Schlüssel aktualisiert:
+
+
+```php
+$template->set_attribute('key', 'value');
+$template->set_attribute('title', 'former title');
+
+
+$attributes = [];
+$attributes['title'] = "a title";
+$attributes['content'] = "some content";
+
+$template->set_attributes($attributes);
+```
+
+Während also das #set_attributes den alten Wert des Attributs 'title' gegen den neuen Wert ersetzt, bleibt das Attribut 'key' erhalten.
+
+Nun bleiben also noch die Methoden #set_layout und #render.
+
+Die erste Methode #set_layout wurde schon in Beispiel 3 vorgestellt und hat als einzigen Parameter das Template, welches als Layout-Template verwendet werden soll.
+
+Die letzte Methode #render wurde auch schon verwendet, hat allerdings zwei zusätzliche Parameter, die bisher nicht gezeigt wurden. Diese dienen aber lediglich dem Komfort. Während wir bisher folgende Verwendung gesehen haben:
+
+
+```php
+$template = $factory->open('hello_world');
+
+$template->set_attribute('name', 'Axel');
+
+$template->set_layout('layout');
+
+echo $template->render();
+```
+
+kann mit dieser Code mit den zwei zusätzlichen Methodenparametern so geschrieben werden:
+
+```php
+$template = $factory->open('hello_world');
+echo $template->render(['name' => 'Axel'], 'layout');
+```
diff --git a/docs/docs/quickstart/trails.md b/docs/docs/quickstart/trails.md
new file mode 100644
index 0000000..ad9582b
--- /dev/null
+++ b/docs/docs/quickstart/trails.md
@@ -0,0 +1,253 @@
+---
+title: Trails für die Kernentwicklung
+sidebar_label: Trails (Web-Framework)
+---
+
+Trails ist ein eigenständiges MVC-Framework, welches in Stud.IP fertig konfiguriert zur Verfügung steht.
+
+> **Achtung**: Die folgende Dokumentation bezieht sich auf die Verwendung von Trails für die Stud.IP-Kernentwicklung. Für die Verwendung von Trails in Plugins lesen Sie bitte [Trails in Plugins](TrailsInPlugins).
+
+## Trails & Stud.IP-Kern
+
+Im Folgenden gibt es eine kleine Einführung zur Entwicklung von Trails-Seiten in Stud.IP.
+
+Trails folgt dem [MVC-Paradigma](http://de.wikipedia.org/wiki/Model_View_Controller). Diesem Paradigma folgend gibt es in Stud.IP im Hauptverzeichnis einen Ordner namens `app`, welcher die Unterordner `controllers` und `views` besitzt. Die Models finden sich hingegen unter `lib/models`.
+
+## Die Struktur
+
+Der Controller ist der Dreh- und Angelpunkt für eine Seite.
+
+Einen neuen Controller erstellt man im Verzeichnis `app/controllers`. Im einfachsten Fall erstellt man dort direkt eine PHP-Datei. Handelt es sich um eine größere Sammlung von Controllern, kann man auch Unterverzeichnisse erstellen. Die dortige Pfadstruktur überträgt sich dabei 1:1 auf die URL.
+
+Ein Controller sollte nie direkt Zugriff auf die Datenbank nehmen und auch sonst so wenig wie möglich Datenstrukturen generieren. Er ist dafür zuständig, die korrekten Daten aus dem richtigen model in die view zu schaffen. Models liegen im passenden Verzeichnis, nämlich 'app/models' und müssen zur Verwendung mittels `require_once` im Controller inkludiert werden. Ab Stud.IP Version 2.5 werden Models aus `app/models` automatisch geladen.
+
+Ausgaben passieren prinzipiell nur innerhalb der view in Templates. Die Templates liegen dabei in `app/views` und haben darunter folgende Pfadstruktur `/pfad_zum_controller/name_des_controllers/name_der_action.php`
+
+## Beispiel
+
+Um das ganze Konzept und die Möglichkeiten zu veranschaulichen wird im Folgenden en detail eine Beispiel-Trails-Seite erklärt.
+
+## Der Controller
+
+### Nur in Stud.IP Eingeloggte oder frei verfügbar?
+
+`app/controllers/example/page.php`
+
+```php
+require_once 'app/controllers/authenticated_controller.php';
+require_once 'app/controllers/studip_controller.php';
+
+class Example_PageController extends AuthenticatedController {
+```
+
+Die erste Entscheidung, die man treffen muss, ist, ob man diesen Controllern nur als eingeloggter Nutzer sehen kann oder auch wenn man nicht eingeloggt ist. Dafür entscheidet man sich einfach für eine von zwei Klassen, von denen man erbt.
+
+* Wie der Name schon sagt, ist die Klasse `AuthenticatedController` diejenige, die dafür sorgt, dass nur eingeloggte Nutzer diesen Trails-Controller aufrufen können.
+* Erbt man von `StudipController`, so ist eben (erstmal) kein einloggen nötig. Das müsste der Controller dann bei Bedarf selbst tun. Der Klassenname des Controllers richtet sich nach dem Pfad der Datei. Die Datei `controllers/pfad1/pfad2/dateiname.php` muss dann so lauten: `Pfad1_Pfad2_DateinameController`
+
+### Die index_action - Wichtigste Action im Controller
+
+```php
+function index_action($param1 = false, $param2 = false)
+ {
+ // Daten holen
+
+ $this->daten = array('index', 'Hier wird automagisch das in views/exmaple/asite/index.php hinterlegte Template verwendet. Der Dateiname des Templates ist immer gleich der Action');
+ }
+```
+
+Hierbei handelt es sich nun um eine Action. Davon kann es in jedem Controller beliebig viele geben. Die `index_action` hat dabei einen Sonderstatus. Wird in der URL keine Action angegeben, so dient diese als Fallback.
+
+### Das Url-Schema
+
+Diese Action kann in Stud.IP nun wie folgt aufgerufen werden:
+
+[`http://irgendeinstudip/dispatch.php/example/pag/index`](http://irgendeinstudip/dispatch.php/example/pag/index)
+
+Diese Url hat dabei folgendes Schema:
+
+[`http://irgendeinstudip.de/dispatch.php/{pfad/zum/controller}/{name_des_controllers}/{name_der_action}[/parameter1][/parameter2][...]`](http://irgendeinstudip.de/dispatch.php/{pfad/zum/controller}/{name_des_controllers}/{name_der_action}\[/parameter1\]\[/parameter2\]\[...\])
+
+### Templates für Actions
+
+Das Besondere am Trails-Framework ist, dass man sich nicht erst aus der Template-Factory ein Template holen muss, sondern dass (solange man nichts anderes sagt) implizit ein Template, welches zur Action gehört, anzeigt.
+
+Diese Templates liegen unter `app/views` und dort in diesem Fall unter `example/page/index.php`.
+
+Variablen an dieses Template übergibt man, indem man sie mittels `$this` setzt. Im Beispiel oben sieht man, dass `$this->daten` ein Array erhält. Im Template hat man dann automatisch eine Variable `$daten` zur Hand, die die im Controller zugewiesenen Werte enthält. Dazu weiter unten im Ausgabetemplate mehr.
+
+### Manipulation des Kontrollflusses - I
+
+Eine Action kann bei Trails mehr tun, als nur Daten an ein automatisch geladenes Template zu übergeben. Sie kann auch auf den Kontrollfluss einwirken.
+
+Dazu folgende Beispiel-Actions:
+
+```php
+function redirect_action() {
+ $this->redirect('example/asite/helloworld/Hallo Welt! Dieses mal sogar weitergeleitet von redirect!');
+}
+```
+
+```php
+function backendwithmessage_action()
+{
+ // do something
+ $this->flash['nachricht'] = array('message' => array('Diese Nachricht wurde bereits in der delete_action in der reservierten Variable flash gespeichert!'));
+
+ // return to index-action
+ $this->redirect('example/asite/index');
+
+}
+```
+
+Diese Action beinhaltet zwei der wohl wichtigsten Möglichkeiten von Trails.
+
+### Routing in Trails
+
+Zum einen kann man mit `$this->redirect({pfad_zum_controller}/{name_des_controllers}/{action}[/parameter]);` auf eine andere Action in einem beliebigen anderen Controller weiterleiten. Das ermöglicht es einem Actions zu haben, die keine eigene Ausgabe brauchen, da sie z.B. nur einen Eintrag löschen und danach die selbe Seite wieder anzeigen. Gibt es eine neue Aktion, baut man einfach eine weitere Action ein und leitet dann passend weiter.
+
+Seit Stud.IP 5.1 kann `redirect()` genauso wie `url_for()` bzw. `link_for()` benutzt werden. Das heisst, es können beliebig viele Parameter angegeben werden, die dann zu der URL zusammengebaut werden, auf die weitergeleitet werden soll. Einzige Ausnahme: Es können keine URLs **und** weitere Parameter übergeben werden.
+
+### Persistente Werte
+
+Die Möglichkeit des Routens führt uns direkt zu einem weiteren Aspekt von Trails. Was nun, wenn so eine "verdeckt" operierende Aktion eine Statusmeldung auf der Hauptseite, zu der sie hin-routet haben möchte? Für diesen und ähnliche Zweck gibt es die spezielle Variable _flash_.
+
+Dieser Variablen kann man direkt einen Wert oder einen Wert an einer Stelle in einem Array zuweisen (wie im Beispiel verwendet). Dieser Wert bleibt nun solange in der Variable `$flash` gespeichert, bis er ausgelesen wird.
+
+In einer Action kann man dann dort mittels `$this->flash` zugreifen, im Template einfach `$flash`.
+
+### Manipulation des Kontrollflusses - II
+
+Außer `$this->redirect` gibt es noch weitere Möglichkeiten zum Eingriff in den Kontrollfluß.
+
+```php
+function helloworld_action($text = 'Hallo Welt!') {
+ $this->render_text(
+ 'helloworld, $this->render_text(\*. htmlReady(urldecode($text)) .'\')<br>' .
+ 'Hier wird das einfach nur Text ausgegeben, ohne Layout<br><br>' .
+ htmlReady(urldecode($text))
+ );
+}
+```
+
+Ruft man `$this->render_text(...)` auf so wird nur der angegebene Text ohne jeglichen Stud.IP-Kontext ausgegeben.
+
+```php
+function index2_action() {
+ $this->daten = array('index2, $this->render_action(\'index\')', 'Hier wird das Template für eine Action in diesem Controller gerendert, mit Layout');
+ $this->render_action('index');
+}
+```
+
+`$this->render_action(*action*)` ruft das Template für eine Action in diesem Controller auf und gibt es mit Stud.IP-Kontext aus.
+
+```php
+function index3_action() {
+ $this->daten = array('index3: $this->render_template(\'example/asite/index\')', 'Hier wird nur ein Template aus view gerendert, ohne Layout');
+ $this->render_template('example/asite/index');
+}
+```
+
+`$this->render_template(*pfad_zum_controller*/*name_des_controllers*/*name_des_templates*)` gibt das angebgene Template ohne Stud.IP-Kontext aus.
+
+```php
+function nihilist_action()
+{
+ $this->render_nothing();
+}
+```
+
+Mit `$this->render_nothing()` sagt man Trails: Bitte kein Template ausgeben.
+
+## Die View
+
+Die folgende Datei ist die view für unser Beispiel.
+
+### Variablenzugriff und Partials
+
+```php
+// Ausgeben der im Controller gesetzten Variable
+var_dump($daten);
+?>
+
+<!-- Ein wenig Text/HTML -->
+<b>Huhu</b>
+
+<!-- Ein partial-Template -->
+<?= $this->render_partial('example/asite/_feedback'); ?>
+```
+
+`var_dump($daten)` gibt das aus, was wir im Controller mittels `$this->daten=*...*` zugewiesen haben. Auf diese Art und Weise gelangen vorbelegt Variablen ins Template.
+
+`$this->render_partial(*template*)` kennt man schon von den normalen Templates. Es erlaubt einem, innerhalb eines Templates ein Subtemplate, ein sogenanntes partial zu inkludieren und anzeigen. Der Inhalt dieser partials wird weiter unten erklärt.\\ Besonders beachten sollte man, das `render_partial` einem einen String zurückliefert, den man erst noch ausgeben muss. In unserem Beispiel geschiet dies mittels `<?=`.
+
+### URLs zu Aktionen in Controllern
+
+```php
+<br>
+So erhält man einen Pfad zu einem Controller:<br>
+$controller->url_for('example/asite/backendwithmessage');<br>
+<br>
+<a href="<?= $controller->link_for('example/asite/backendwithmessage') ?>"><button>Ausprobieren</button></a>
+```
+
+`$controller->url_for('path_to_action')` ist die wohl wichtigste Funktion innerhalb eines Templates. Wie der Name schon andeutet, erhält man hier eine URL zu einer bestimmten Action in einem bestimmten Controller.\\ Dies ist die URL, die man in Formulare, Links, etc. hineinsteckt, wenn man sich innerhalb von Trails bewegen möchte.
+
+Zu beachten ist, dass `$controller->link_for('path_to_action')` an den Stellen genutzt werden sollte, wo ein Link ausgegeben wird und `$controller->url_for('path_to_action')` an den Stellen, wo die URL noch von einer weiteren API verwendet wird.
+
+Ab Stud.IP 4.3 können Links zu Controller-Aktionen auch dadurch erzeugt werden, indem man `$controller->*action*(<parameter>)` aufruft. Der Pfad zum Controller und der Controller selbst müssen somit nicht mehr angegeben. Eine entsprechende URL erhält man durch das Anhängen von `URL` an die aufgerufene Methode: `$controller->*action*URL($parameter);`
+
+`$controller->url_for()` nimmt eine beliebige Anzahl von Parametern an (solange dies keine URL ist) und baut daraus eine korrekte URL zu der Controller-Action zusammen. Insbesondere gelten folgende Regeln:
+
+- Ein Aufruf von `$controller->url_for()` ohne Parameter erzeugt eine URL zu der aktuell aufgerufenen Action.
+- Ist der letzte Parameter ein Array, so werden die Werte des Array als GET-Parameter an die URL gehängt.
+- Wird ein [SimpleORMap](SimpleORMap)-Objekt übergeben, so wird dieser Parameter durch die Id des Objektes ersetzt.
+
+### Zugriff auf persistente Werte im Template
+
+`app/views/example/asite/_feedback.php`
+
+```php
+if ($flash['nachricht']['message']) {
+ foreach ($flash['nachricht']['message'] as $nachricht) {
+ echo MessageBox::info($nachricht);
+ }
+}
+```
+
+Dies ist das oben bereits genannte partial. Partials haben automatisch Zugriff auf alle Variablen ihres Eltern-Templates (dort wo render_partial gesagt wurde). In diesem speziellen Fall wird auf die automagische Variable `$flash` zugegriffen, die in der `backendwithmessage_action` definiert hatten.
+
+## Trails und SimpleORMap (ab Stud.IP 4.3)
+
+Ab Stud.IP 4.3 sind Trails und SimpleORMap etwas näher miteinander verknüpft. Als Parameter von `link_for()` und `url_for()` bzw. den kurzen `*action*()` bzw. `*action*`URL()@@ Aufrufen können direkt die SimpleORMap-Objekte übergeben werden, wodurch ihre Id als Parameter genutzt wird:
+
+```php
+$controller->link_for('controller/edit', $sorm);
+// bzw.
+$controller->edit($sorm);
+```
+
+Die Parameter der Action am Controller können ebenfalls direkt SimpleORMap-Objekte zurückgeben, indem sie einen entsprechenden Typehint erhalten. In dem Fall wird die übergebene Id genutzt, um das Objekt zu laden. Solange der entsprechende Parameter nicht optional ist (`= null`), wird `SimpleORMap::find()` verwendet, um das Objekt zu laden. Ist das Objekt als optional markiert, wird das Objekt mittels `new *SORM*($id);` erzeugt.
+
+Über die Eigenschaft `_autobind` am Controller kann gesteuert werden, ob die derart erzeugten Objekte auch automatisch mittels des Namens des Parameters an den View gebunden werden, damit sie dort auch verfügbar sind.
+
+Ein Beispiel hierfür:
+
+```php
+# Controller
+
+ protected $_autobind = true;
+
+ // ...
+
+ public function edit_action(SORM $sorm)
+ {
+ }
+
+# View
+
+<label>
+ <?= _('Titel') ?>
+ <input type="text" name="title" value="<?= htmlReady($sorm->title) ?>">
+</label>
+```
diff --git a/docs/docs/quickstart/troubleshooting.md b/docs/docs/quickstart/troubleshooting.md
new file mode 100644
index 0000000..56cd5f7
--- /dev/null
+++ b/docs/docs/quickstart/troubleshooting.md
@@ -0,0 +1,56 @@
+---
+id: troubleshooting
+title: Troubleshooting
+sidebar_label: Troubleshooting
+---
+
+### PHP generell
+
+#### Falsche Zeitdifferenzen? Bei Zeitdifferenzen gmdate() statt date() verwenden!
+
+Zeitangaben in Stud.IP werden häufig als Sekunden in der Unix-Zeit (Sekunden seit 1.1.1970 0:00:00 Uhr UTC) angegeben. Da es sich um Zeitstempel in normalen positiven Ganzzahlen handelt, lässt sich problemlos damit rechnen und durch eine einfache Subtraktion die Zeitdifferenz zwischen zwei Zeitangaben bilden.
+
+Da die Differenz zweier Zeitangaben in der Unix-Zeit, also in UTC, vorliegt, muss zur Ausgabe statt date() die Funktion gmdate() verwendet werden, da date() die Zeitzone des übergebenen Zeitstempels mitbetrachtet. Am 1.1.1970 galt die mitteleuropäische Zeitzone (UTC + 1 Stunde), wodurch date() eine Stunde auf die Differenz aufaddiert. gmdate() hingegen ignoriert die Zeitzone und liefert die korrekte Zeitdifferenz.
+
+### Stud.IP Klassen und Methoden
+
+#### in URLs mit Parametern wird `&` durch `&amp;` ersetzt
+
+Warscheinlich wurde die Methode getLink() der URLHelper-Klasse verwendet. Der Unterschied zwischen getLink() und getURL() besteht darin, dass getLink() alle Zeichen, welche in HTML-Code problematisch sein könnten, durch ihre HTML-Entitäten ersetzt. Die HTML-Entität von `&` ist `&amp;` (für ampersand).
+
+Zur Lösung des Problems wird getLink() einfach durch getURL() ersetzt.
+
+
+### SimpleORMap
+#### Fehlermeldung "Invalid argument supplied for foreach()" in SimpleORMap
+
+Es tauchen die folgenden Fehlermeldungen auf:
+
+```shell
+Warning: Invalid argument supplied for foreach() in /var/www/studips/studip-trunk/lib/models/SimpleORMap.class.php on line 1619
+
+Warning: in_array() expects parameter 2 to be array, null given in /var/www/studips/studip-trunk/lib/models/SimpleORMap.class.php on line 1311
+```
+
+Das Problem tritt auf, wenn auf eine Datenbanktabelle zugegriffen wird, die keinen Primärschlüssel (welcher meistens "id" heißt) besitzt. Um einen Primärschlüssel nachzurüsten, führt man in der MySQL-Konsole folgenden Befehl auf die betroffene Tabelle aus:
+`ALTER TABLE tabellenname ADD PRIMARY KEY(id);`
+
+Anschließend muss der Stud.IP-Cache gelöscht werden. Liegt dieser unter /tmp/studip-cache/ so werchselt man in dieses Verzeichnis und führt `rm -rf ./*` aus, um den gesamten Inhalt von /tmp/studip-cache/ zu löschen.
+
+#### Fehlermeldung "Passed variable is not an array or object, using empty array instead" bei Benutzung einer SimpleORMap-Relation
+
+Dieses Problem kann auftauchen, wenn man versucht, im Plugin auf eine SimpleORMap-Relation zuzugreifen, welche mit "has_many" definiert wurde. Der Fremdschlüssel wurde über den Parameter "foreign_key" korrekt gesetzt, der assoziierte Fremdschlüssel (Parameter "assoc_foreign_key") wurde ebenfalls richtig gesetzt, wird aber nicht beachtet.
+
+Das Problem besteht darin, dass im Controller, in welchem auf die Relation zugegriffen wird, die Zielklasse der Relation nicht eingebunden wurde. Bindet man die Zielklasse der "has_many" Relation ein, so verschwindet das Problem. Andere Arten von SimpleORMap-Relationen können ebenfalls von dem Problem betroffen sein.
+
+
+#### RuntimeException mit der Nachricht: "assoc_func: ModellKlasse::findBySomeAttribute is not callable"
+
+Bei der Benutzung einer "has_many"-Relation kann diese Fehlermeldung beim Zugriff auf die Relation auftauchen. Das Problem besteht darin, dass das Modell, welches das Ziel der Relation darstellt, nirgendwo eingebunden ist und somit die Modellklasse nicht bekannt ist.
+
+Zur Lösung wird einfach die Modellklasse mittels require_once eingebunden. Am besten wird dies in der Datei getan, in welcher auf die Relation zugegriffen wird.
+
+
+#### Meine Dateitypen oder ähnliche vom Plugin bereitgestellte Strukturen sind in der API nicht verfügbar
+
+Die APIs (REST und JSON) laden nicht alle Systemplugins. Wenn über ein Systemplugin neue Datenstrukturen bereitgestellt werden, muss das Plugin auch das Interface `RESTAPIPlugin` bzw. `JsonApi\Contracts\JsonApiPlugin` implementieren, damit diese Strukturen auch innerhalb von API-Aufrufen verfügbar sind.
diff --git a/docs/docs/quickstart/ueberblick.md b/docs/docs/quickstart/ueberblick.md
new file mode 100644
index 0000000..6ced26b
--- /dev/null
+++ b/docs/docs/quickstart/ueberblick.md
@@ -0,0 +1,78 @@
+---
+title: Überblick
+slug: /quickstart/
+---
+
+Herzlichen Glückwunsch! Wenn du diese Seite aufgerufen hast, bist du
+kurz davor, mit der Stud.IP-Entwicklung loszulegen. Ja, du! Nicht verschämt weggucken oder gar Angst vor der großen Aufgabe haben:
+Wer sich grundlegend mit PHP und MySQL auskennt und bereit ist, sich zunächst mit ein paar Grundsätzen der Stud.IP-Entwicklung vertraut zu machen,
+bringt alles mit, was nötig ist.
+
+## Entwicklungsforum
+
+Stud.IP ist ein Open-Source-Projekt. Das heißt bei uns: Die Entwickler*innen bestimmen frei und aus eigener Motivation,
+wo's langgeht und organisieren sich selbst.
+Der allererste Schritt: Besorge dir einen Account auf dem [Developer-Server](http://develop.studip.de/studip).
+Da laufen alle Diskussionen rund um die Weiterentwicklung. Dein erster Anlaufpunkt ist das "Developer-Board".
+Hier kannst du Fragen loswerden und deine Ideen präsentieren. Nur Mut, einfach mal Hallo sagen.
+
+## Berechtigungen
+
+Open-Source heißt nicht automatisch: Chaos. Auch bei uns gibt es Menschen, die das letzte Wort haben dürfen und
+dafür mehr Verantwortung tragen müssen als andere.
+
+**Das ist die Core-Group**.
+
+Zur Zeit sind das ca. 16 Menschen, die schon lange dabei sind und durch anhaltendes persönliches Engagement bewiesen haben,
+dass ihnen Stud.IP am Herzen liegt. Die meisten werden von ihrem Arbeitgeber, z.B. einer Universität, die Stud.IP einsetzt,
+dafür bezahlt, dass sie an Stud.IP mitarbeiten.
+Voraussetzung ist das aber nicht und die Mitgliedschaft ist an Personen gebunden, nicht an Arbeitgeber:
+Wie die Bundestagsabgeordneten sind die Core-Group-Mitglieder nur ihrem Gewissen verpflichtet.
+
+Die Core-Group-Mitglieder entscheiden demokratisch, welche Weiterentwicklungen in das Stud.IP-Release aufgenommen
+werden und welche Regeln für die Entwicklung gelten. Dafür sind sie verpflichtet, sorgfältig zu testen, regelmäßig
+auf +dem Developer-Server mitzulesen und sich mit allen Fragen der Weiterentwicklung zu beschäftigen.
+
+Am Anfang deiner Entwicklungstätigkeit wirst du noch nicht zur Core-Group gehören. Das heißt: Andere schauen sich an, was du entwickelst und entscheiden darüber, ob es so wie's ist, in die offizielle Stud.IP-Version aufgenommen wird. Das geschieht natürlich transparent und für dich jederzeit nachvollziehbar. Du wirst, wenn du die Hinweise hier befolgst, nicht mit einer fertigen, arbeitsreichen Entwicklung dastehen und hören: "Nein, das wollen wir nicht." Aber das heißt auch: Du musst dich nicht um alles kümmern, sondern kannst dir die Bereiche aussuchen, in denen du dich besonders kompetent fühlst oder die dir besonders viel Spaß machen. Wenn du durch Engagement und brauchbare Ideen und Beiträge auffällst, wirst du über kurz oder lang gefragt werden, ob du mehr Verantwortung übernehmen und Core-Group-Mitglied werden willst.
+
+## Personengruppen
+
+Diese Anleitungen beschreibt die Aspekte der Stud.IP-Entwicklung, die im engeren Sinne als "Programmieren" bezeichnet werden.
+Das Stud.IP-Projekt braucht aber nicht nur Informatiker*innen und
+PHP-Hacker, sondern ganz dringend auch engagierte Menschen mit anderen Interessen und Fähigkeiten. Das sind z.B.:
+
+| Gruppe | Beschreibung |
+| ---- | ---- |
+| Tests | Ständig entsteht neues und die Programmierer selbst sind oft schlechte Tester. Zu eng ist der Blick auf das selbst entwickelte, als dass die Art und Weise, wie der "normale Nutzende" mit dem System umgeht immer gleich gut berücksichtigt werden könnte. Wenn du Spaß daran hast, neue Funktionen auf Herz und Nieren zu testen und gründlich zu meckern: Im Developer-Board melden!|
+| Grafik und Design | Stud.IP kommt mit dem Anspruch daher, ansprechend gestaltet zu sein. Dafür brauchen wir Menschen, die sich gern mit Farben, Icons, Bedienelementen, Fotos und Schriftarten beschäftigen. Wenn du Verbesserungsvorschläge hast, dich etwas gewaltig stört oder du dich mit Bildbearbeitung und Webdesign beschäftigen willst: Im Developer-Board melden! |
+| Sprachkunst | Hinweistexte, Fehlermeldungen und andere Texte in Stud.IP müssen prägnant und treffend formuliert werden. Dazu kommt die Hilfe und die Übersetzung ins Englische oder andere Sprachen. Wenn du dich auf dieser Spielwiese tummeln möchtest: Im Developer-Board melden! |
+| Didaktik und Pädagogik | Stud.IP ist eine E-Learning-Anwendung. Studierende, Lehrende und andere Anwender*innen sollen es als Werkzeug nutzen, um Lehr- und Lernprozesse zu gestalten. Wenn du dazu Ideen, Verbesserungsvorschläge und Anregungen hast: Im Developer-Board melden! |
+
+
+Der Rest dieser Anleitung ist tatsächlich für diejenigen gedacht, die
+selbst im Dreck wühlen möchten, also mit PHP, JavaScript und SQL umgehen wollen.
+Alle anderen fühlen sich bitte nicht ausgegrenzt, sondern ins Developer-Board verwiesen.
+
+## Technische Grundlage
+
+Stud.IP ist eine **PHP**-Anwendung, die eine **MySQL/MariaDB*-Datenbank verwendet.
+Wer mitentwickeln will, braucht also vor allem PHP-Kenntnisse, muss sich etwas mit SQL auskennen und ein bisschen über Apache- oder Nginx-Konfiguration wissen.
+Und, wie immer bei Webanwendungen: Alle Ausgaben geschehen in HTML, formatiert durch CSS (SCSS und LESS).
+Einige Funktionen verwenden zudem JSON als Zwischenformat, JavaScript und AJAX sind ebenfalls an vielen Stellen präsent.
+Wenn all das keine Fremdwörter für dich sind, bis du gut gerüstet.
+
+## Git-Repository
+
+Da viele Entwickelnde am verschiedensten Orten an Stud.IP
+mitentwickeln, wird ein Versionsverwaltungssystem eingesetzt. Wir
+haben uns für Git (Gitlab) entschieden. Alle Infos, die für den Start wichtig sind, findest du auf der Seite [Entwicklungssystem aufsetzen](Entwicklungsumgebung).
+
+## BIESTs, StEPs und Lifters
+
+Qualitätssicherung wird bei Stud.IP großgeschrieben. Die Entwickelnden haben sich ein umfangreiches Regelwerk gegeben, mit dem sie die verschiedenen Anforderungen bei der Weiterentwicklung überschaubar machen. Drei Begriffe stehen dabei für die wesentlichsten Vorgänge:
+
+| Typ | Beschreibung |
+| ---- | --- |
+| BIESTs | Ein Biest ist ein erkannter Fehler in der Software. Fies und unbedingt zu elimieren. Im "Bug-Board" auf dem Developer-Server werden alle Fehler gesammelt und warten dann auf ihre Erledigung. Wenn du glaubst, einen Fehler entdeckt zu haben, kannst du ihn dort über ein Formular melden. |
+| StEPs | Weiterentwicklung heißt: Neue Funktionen hinzufügen, vorhandene ändern. Wer eine Idee dazu hat, formuliert sie in einem "Stud.IP Enhancement Proposal" (kurz: StEP). Im "StEP-Forum" auf dem Developer-Server werden die StEPs dann diskutiert: Ist der Vorschlag sinnvoll, ist alles bedacht und gibt es einen sinnvollen Plan zur Umsetzung? Schau dich dort im Wiki um, um die aktuellen StEPs, d.h. die konkret geplanten Weiterentwicklungen, anzuschauen. Wenn du einen eigenen Vorschlag einbringen willst, musst du dir einen "Paten" aus der Core-Group suchen. Das ist keine Schikane, sondern hilft uns, Ideen zu sortieren und Neueinsteigern bei der Orientierung zu helfen. |
+| Lifters | Manche Umbauten lassen sich nicht auf einen Schlag erledigen. StEPs müssen immer zu einem bestimmten Release vollständig umgesetzt sein, grundlegendere Umbauarbeiten brauchen aber manchmal länger. Beispiel: Die vollständige Umstellung auf ein Template-System. Am Anfang wirst du vermutlich keine eigenen Lifters ("Laufende Technikrenovierung für Stud.IP") formulieren, aber die vorhandenen Lifters sind für alle Entwickler verbindlich. In dieser Anleitung wird jeweils auf die Lifter-Dokumentation verwiesen, wenn du eine Konvention bei Entwicklung beachten musst. |
diff --git a/docs/docs/quickstart/url-helper.md b/docs/docs/quickstart/url-helper.md
new file mode 100644
index 0000000..efddbf6
--- /dev/null
+++ b/docs/docs/quickstart/url-helper.md
@@ -0,0 +1,117 @@
+---
+id: url-helper
+title: URLHelper
+sidebar_label: URLHelper
+---
+
+## Benutzung der Klasse URLHelper
+
+Zur Vereinfachung der Umstellung von vorhandenem PHP-Code auf Tabbed-Browsing und um allgemein die Verwendung von Session-Variablen auf ein sinnvolles Maß zurückzuführen, wurde mit dem Lifter001 die Klasse `URLHelper` eingeführt. Neuer Code muß diese Klasse verwenden, um Verweise auf andere Seiten (oder die gleiche Seite) in Stud.IP zu erzeugen. Externe Links und Bereiche von Stud.IP, die bereits eigene Funktionen zur Link-Erzeugung verwenden, sind von der Verwendung des `URLHelper` ausgenommen. Das sind im einzelnen:
+
+* Links auf externe Seiten (z.B. die Hilfe)
+* Links auf statische Inhalte (Bilder, Videos usw.)
+* Links auf Aktionen in einem Plugin (`PluginEngine::getLink()`)
+* Links auf Dokumente im Download-Bereich (`GetDownloadLink()`)
+* Links auf Trails-Controller (`Trails_Controller::url_for()`)
+
+Alle anderen Links - insbesondere auch Links aus Plugins auf Seiten im Stud.IP-Kernsystem - müssen so umgestellt werden, daß sie die Klasse `URLHelper` zur Erzeugung der URL verwenden.
+
+### Allgemeines
+
+Hauptzweck dieser Klasse ist es, alle Links auf einer Seite nach Bedarf um zusätzliche URL-Parameter erweitern zu können, ohne immer wieder alle Links anpassen zu müssen. Insbesondere bei durch Hilfsfunktionen oder -klassen erzeugten Links wäre so ein Anpassen teilweise auch überhaupt nicht (sinnvoll) möglich.
+
+Die Grundidee dabei ist relativ simpel: Es gibt eine globale Liste von "automatischen" - d.h. bei der `URLHelper`-Klasse registrierten - Link-Parametern sowie eine Hilfsfunktion *getLink()*, die eine übergebene URL mit diesen registrierten Parametern versieht. Der Aufrufer von `getLink()` muß sich also nicht darum kümmern, welche zusätzlichen Parameter gerade eingebaut werden sollen. In Stud.IP wird dieser Mechanismus zum Beispiel dafür verwendet, um die aktuell gewählte Veranstaltung oder die auf einer Seite eingestellten Ansichtsoptionen bei jedem Klick weiterzureichen, ohne diese serverseitig in der Session speichern zu müssen (was bei Tabbed-Browsing unweigerlich zu Problemen führen würde).
+
+Ein einfaches Beispiel könnte so aussehen:
+
+```php
+// $view enthält die gewählte Ansicht
+URLHelper::addLinkParam('view', $view)
+
+[...]
+
+switch ($view) {
+ case 'show': // normale Ansicht der Seite
+ [...]
+ case 'search': // Suchergebnisse anzeigen
+ [...]
+ case 'edit': // Seite bearbeiten
+ [...]
+}
+
+[...]
+
+// Ausgabe erzeugen (kann auch im Template sein)
+echo '<a href="'.URLHelper::getLink(*, array('page' => 25)).'">...</a>';
+```
+
+Der aktuelle Inhalt der Variablen `$view` wird dann automatisch zu dem so erzeugten Link hinzugefügt und man erhält so etwas wie:
+
+```php
+<a href="?page=25&amp;view=edit">
+```
+
+Natürlich kann jeder Link auch eigene Parameter enthalten, die spezifisch für diesen Link sind. Diese würden dann direkt im Aufruf von `getLink()` angegeben und nicht global als Parameter registriert. Lokal im Aufruf angegebene Parameter haben dabei Vorrang vor den global registrierten, d.h. man kann bei bei Bedarf auch für einzelne Links registrierte Parameter mit anderen Werten versehen oder ganz ausblenden (Parameter beim Aufruf auf `NULL` setzen).
+
+### Methoden der Klasse `URLHelper`
+
+An dieser Stelle sind die wichtigsten Operation der Klasse `URLHelper` gesammelt und dokumentiert. Es handelt sich dabei jeweils um *Klassenmethoden*, d.h. der Aufruf erfolgt über `URLHelper::`*Name*.
+
+* **addLinkParam($name, $value)**
+
+ Registriert einen Link-Parameter mit dem angegeben Namen und Wert. Sollte es bereits einen Parameter gleichen Namens geben, wird der alte Wert durch den neuen ersetzt. Eine ggf. vorhandene Bindung (siehe `bindLinkParam()`) wird aufgehoben.
+
+* **bindLinkParam($name, &$var)**
+
+ Bindet einen Link-Parameter an die angegebene PHP-Variable. Sollte es bereits einen Parameter gleichen Namens geben, wird der alte Wert durch die Bindung ersetzt. Der konkrete Wert dieses Parameters wird im Unterschied zu `addLinkParam()` bei dieser Operation nicht direkt gesetzt, sondern erst *beim Aufruf* von `getLink()` oder `getURL()` durch Auslesen der angegebenen Variable ermittelt. Ändert man also nach dem Aufruf von `bindLinkParam()` den Wert dieser Variable, wird immer der gerade aktuelle Wert verwendet.
+
+ Außerdem wird die angegebene Variable durch diesen Aufruf mit dem Wert des URL-Parameters in der REQUEST-Umgebung der Seite initialisiert. Diese Funktion ist vor allem dazu nützlich, bisher in Session-Variablen gespeicherten Zustand in URL-Parameter auzulagern.
+
+* **removeLinkParam($name)**
+
+ Entfernt einen zuvor registrierten Link-Parameter wieder. War kein Parameter dieses Namens registriert, passiert nichts.
+
+* **getLinkParams()**
+
+ Liefert eine Liste (ein Array mit Name/Wert-Paaren) aller derzeit registrierten Parameter. Damit könnte man diese z.B. als *hidden*-Felder in eine FORM einbauen, um Längenbeschränkungen der URL-Parameter aus dem Weg zu gehen.
+
+* **getLink($url = *, $params = NULL)**
+
+ Ergänzt die übergebene URL um alle aktuell registrierten Parameter. Im Falle von an Variablen gebundenen Parametern wird der zum Zeitpunkt des Aufrufs aktuelle Wert der jeweiligen Variable eingesetzt. Wird der zweite (optionale) Parameter übergeben, können weitere Parameter gesetzt werden, deren Werte ebenfalls zur URL hinzugefügt werden.
+
+ Im Falle von gleichnamigen Parametern gilt: Einträge im `$params`-Array haben Vorrang vor Parametern in der `$url`. Parameter aus der übergebenen `$url` haben Vorrang vor registrierten Parametern. Möchte man einen registrierten Parameter komplett aus der URL ausblenden, so muß man diesem im `$params`-Array einen Wert von `NULL` geben.
+
+ Das Resultat dieser Funktion ist eine *entity-kodierte URL*, d.h. es kann direkt in Attribute im HTML eingesetzt werden (*action* einer FORM, *href* eines A-Elements). Braucht man die unkodierte URL, sollte `getURL()` verwendet werden.
+
+* **getURL($url = *, $params = NULL)**
+
+ Diese Funktion arbeitet genau wie `getLink()`, liefert aber keinen entity-kodierten Wert zurück, sondern die unkodierte URL. Diese kann dann z.B. für Aufrufe über JavaScript verwendet werden.
+
+### Probleme durch URL-Parameter
+
+Durch die Verwendung des `URLHelper` können auch neue Proleme auftreten, die bei der Nutzung von Session-Variablen nicht oder nicht im dem Maße bestehen. Es eignen sich auch nicht alle Arten von Session-Daten zur Übergabe über die URL, so daß man im Einzelfall abwägen muß, ob eine Umstellung sinnvoll ist. Dabei sollten die folgenden Punkte berücksichtigt werden:
+
+* Längenbeschränkung von URLs
+
+ Es gibt eine browser-abhängig maximale Längenbeschränkung von URLs in der Größenordnung von einigen tausend Zeichen (typischerweise 2048 Zeichen beim Internet Explorer, 8192 Zeichen bei Firefox). Längere URLs werden abgeschnitten und führen damit zum Verlust von Informationen. Möchte man also einen sehr komplexen Zustand über die URL übergeben - z.B. eine beliebig große Menge an aufgeklappten Knoten in einer Baumansicht - sollte man dies ggf. besser serverseitig speichern und nur einen Verweis auf eine gespeicherte Konfiguration in der URL hinterlegen.
+
+* Manipulation von URL-Parametern durch den Nutzer
+
+ Man sollte sich grundsätzlich der Tatsache bewußt sein, daß URL-Parameter - im Gegensatz zu Session-Daten - vom Nutzer nach belieben Verändert werden können, man darf also niemals darauf vertrauen, daß diese vom Nutzer nicht manipuliert werden. Würde man also bisher in der Session gespeicherte Berechtigungen eines Nutzers in die URL verschieben, so müßte man diese bei jedem Seitenaufruf immer neu prüfen.
+
+* Namenskollisionen bei Parametern verschiedener Seiten
+
+ Der `URLHelper` unterscheidet beim Erzeugen der URLs nicht, auf welche Seite ein Link verweist. Es ist auch nicht möglich, Parameter nur für bestimmte Ziele zu registrieren (da in Stud.IP in zunehmendem Maße Dispatcher eingesetzt werden, würde das auch nicht viel bringen). Daher sollte man darauf achten, Namenskollisionen bei über den URLHelper verwalteten Parametern verschiedener Seiten zu vermeiden, entweder durch Verwendung eindeutiger Präfixe (*wiki_search*) oder durch Abgleich mit der Liste bereits verwendeter Namen.
+
+### Beispiele
+
+Hier sollten einige kleine Bespiele für die Verwendung des `URLHelper` aus dem praktischen Einsatz in Stud.IP gesammelt werden. Leider gibt es hier noch nicht viel...
+
+```php
+$link = URLHelper::getLink('wiki.php', array('keyword' => $keyword));
+echo '<a href="'.$link.'">'.htmlReady($keyword).'</a>';
+```
+
+### URLHelper für Javascript
+
+Unabhängig hiervon gibt es auch einen [URLHelper für Javascript](HowToJavascript), der eine ähnliche API aufweist und auch ähnliches tut. Jedoch sind diese beiden URLHelper nicht aufeinander abgestimmt und völlig unabhängig voneinander.
diff --git a/docs/docs/quickstart/users.md b/docs/docs/quickstart/users.md
new file mode 100644
index 0000000..4414881
--- /dev/null
+++ b/docs/docs/quickstart/users.md
@@ -0,0 +1,189 @@
+---
+title: Users
+---
+
+
+User sind über zwei systemweit eindeutige Kennzeichen zu identifizieren: Die `user_id` und der `username`. Der Username, eine vom Nutzer selbstgewählte oder aus einem zur Authentifizierung genutzten System übernommene Zeichenkette kann sich ändern, die user_id, ein von Stud.IP generierter MD5-Hash nicht.
+
+Informationen über den Nutzer, für den das Script gerade ausgeführt wird, findet sich im global bekannten Objekt `$user`, das von `page_open()` initialisiert wird.
+
+Darüber lassen sich verschiedene Informationen gewinnen:
+
+| Wert | Beschreibung
+| ---- | ---- |
+| `$GLOBALS['user']->id` | `user_id` des aktuellen Nutzers |
+| `$GLOBALS['user']->username` | `username` des aktuellen Nutzers |
+
+:::warning Anmerkung
+
+In Stud.IP-Installationen älter als v2.2 wurde inzwischen veraltetet so auf diese Daten zugegriffen: `$auth->auth["uid"]` bzw. `$auth->auth["uname"]`
+
+:::
+
+## Berechtigungen
+
+Jeder Nutzer hat verschiedene Rechte im System, die über das global bekannte Objekt `$perm` zugänglich sind,
+das ebenfalls von [`page_open()`](quickstart/Allgemeine-Struktur#page_open) initialisiert wird.
+
+### Globale Rechte
+
+Die globale Rechtestufe wird über die Methode `$perm->get_perm()` erfragt, bzw. über `$perm->have_perm(*<rechtestufe>*)` geprüft.
+Mögliche Werte für Rechtestufen sind:
+* `nobody`
+* `user`
+* `author`
+* `tutor`
+* `dozent`
+* `admin`
+* `root`.
+
+Die Methode`$perm->check(*<rechtestufe>*)` wird wie have_perm verwendet, liefert jedoch im Misserfolgsfall nicht `FALSE` zurück, sondern erzeugt eine Stud.IP-Seite mit einer Zugriffsfehlermeldung und beendet die Ausführung des aktuellen Scripts.
+
+`$perm->have_perm(*<rechtestufe>*)` und `$perm->check(*<rechtestufe>*)` prüfen nicht exakt, sondern betrachten die übergebene Stufe als Mindeststufe.
+
+`$perm->have_perm('dozent')` liefert also `true`, wenn der aktuelle Nutzer die globale Rechtestufe 'dozent', 'admin'
+oder 'root' hat.
+
+
+:::tip ist der eingeloggte Nutzer Admin?
+
+```php
+if ($perm->have_perm('admin')) {
+ ...
+}
+```
+:::
+
+:::tip welche Rechtestufe hat der Nutzer global?
+
+```php
+$p = $perm->get_perm();
+```
+:::
+
+:::note Datenbankinfo
+
+Die globale Rechtestufe ist in `auth_user_md5.perm` gespeichert.
+
+:::
+
+### Sonstige Rechte
+
+In jeder Veranstaltung und jeder Einrichtung kann ein Nutzer über Rechte verfügen, die von seiner globalen Rechtestufe abweichen können.
+Der Zugriff auf veranstaltungs- und einrichtungsbezogene Rechtestufen geschieht über die Methoden
+
+`$perm->get_studip_perm($range_id)` und `$perm->have_studip_perm(*<rechtestufe>*,$range_id)`.
+
+Die `range_id` ist die `seminar_id` oder `institut_id` der zu überprüfenden Veranstaltung oder Einrichtung (jeweils MD5-Hashes).
+
+`$perm->have_studip_perm(*<rechtestufe>*,$range_id)` prüft, ob der Nutzer *mindestens* über die angefragte Rechtestufe in der Veranstaltung
+bzw. der Einrichtung verfügt (s.o.).
+
+
+:::tip ist der eingeloggte Nutzer mindestens Tutor in Veranstaltung `$course_id`?
+
+```php
+if ($perm->have_studip_perm('tutor', $course_id)) {
+ ...
+}
+```
+
+:::
+
+:::tip welche Rechtestufe hat der Nutzer in der Veranstaltung `$course_id`?
+
+```php
+$p = $perm->get_studip_perm($sem_id);
+```
+
+:::
+
+:::note Datenbankinfo
+
+Veranstaltungsbezogene Rechtestufen sind in `seminar_user.perms` abgelegt, die einrichtungsbezogenen Rechtestufen in `institute_user.perms`.
+
+:::
+
+
+:::warning
+
+An vielen alten Stellen des Stud.IP-Codes wird auf die globale Variable `$rechte` zugegriffen, in der als boolscher Wert abgelegt ist, ob der aktuelle Nutzer in der gewählten Einrichtung oder Veranstaltung mindestens über Tutorenrechte verfügt. Diese Variable sollte in neuem Code nicht mehr verwendet werden. Stattdessen ist der Ausdruck `$perm->have_studip_perm('tutor', $SessSemName[1])` zu verwenden.
+
+:::
+
+
+### UserManagement
+
+Die Klasse [`UserManagement`](https://gitlab.studip.de/studip/studip/-/blob/main/lib/classes/UserManagement.class.php) kapselt viele Aktionen, die mit Nutzerdaten regelmäßig durchgeführt werden. Sie ist vor allem entstanden, um die vielfältigen Aktionen rund um Neuanlegen und Löschen eines Nutzers an einer Stelle zusammenzufassen und z.B. Authentifizierungsplugins zugänglich zu machen.
+
+Eine Detaildokumentation der verfügbaren Methoden sollte direkt dem Quellcode entnommen werden: https://gitlab.studip.de/studip/studip/-/blob/main/lib/classes/UserManagement.class.php
+
+Die von der Klasse `UserManagement` berührten Daten liegen vor allem in den Tabellen `auth_user_md5` und `user_info`.
+
+:::warning
+
+Jeglicher Code, der INSERT oder DELETE Statements gegen die Tabellen `auth_user_md5` und `user_info` absetzt, ist als veraltet zu betrachten.
+
+Für alle Aktionen zum Erzeugen und Löschen von Nutzern sollte die Klasse `UserManagement` verwendet werden.
+
+:::
+
+### Voreinstellungen
+
+Die Nutzer können sich "ihr" Stud.IP mit einer Reihe von persönlichen Konfigurationsoptionen verändern. Sei es die
+nach dem Login angezeigte Startseite, die Sprache, die bevorzugte Sortierung aus der Seite "Meine Seminare".
+
+Der für alle Neuentwicklungen vorgesehene Weg führt über die Klasse [`UserConfig`](https://gitlab.studip.de/studip/studip/-/blob/main/lib/classes/UserConfig.class.php). Darüber lassen sich nutzerspezifische Einstellungen abrufen und speichern.
+
+
+
+:::tip Methoden und typische Verwendungsweise der Klasse sind:
+
+```php
+// construct without implicit user/key
+$uc=UserConfig()
+// construct and set implicit user/key
+$uc=UserConfig($someuser_id, 'somekey');
+
+// get value for "someuser" and "somekey"
+
+$v=$uc->getValue();
+
+// get value for "otheruser" and "somekey"
+
+$v=$uc->getValue($otheruser_id, $somekey);
+
+// get value for "someuser" and "otherkey"
+$v=$uc->getValue(null, 'otherkey');
+
+// get value for "otheruser" and "otherkey"
+$v=$uc->getValue($otheruser_id, 'otherkey');
+
+// set value to "somevalue" for "someuser"/"somekey"
+//... combinations of explicit user_id and key same as getValue(...) ...
+$uc->setValue('somevalue');
+
+// unset (delete from db) value for "someuser"/"somekey"
+//... combinations of explicit user_id and key same as getValue(...) ...
+$uc->unsetValue();
+
+// delete all entries for a user (user_id explicit or implicit)
+$uc->unsetAll($user_id);
+
+// get all entries for a user (user_id explicit or implicit)
+$uc->getAll($user_id);
+
+// switch to "otheruser" for implict get/set
+$uc->setUserId($otheruser_id);
+
+// switch to "otherkey" for implict get/set
+$uc->setKey('otherkey');
+
+```
+:::
+
+:::warning
+
+In früheren Codeteilen wurden Nutzereinstellung zumeist in dauerhaften Session-Variablen gespeichert.
+
+:::
diff --git a/docs/docs/quickstart/utf8.md b/docs/docs/quickstart/utf8.md
new file mode 100644
index 0000000..b879648
--- /dev/null
+++ b/docs/docs/quickstart/utf8.md
@@ -0,0 +1,48 @@
+---
+title: UTF-8
+---
+
+Seit Version 4.0 verwendet Stud.IP UTF-8 als Standardkodierung.
+Für Kern- und Plugin-Entwickler gibt es einige Dinge zu beachten, die sich in der Entwicklung ändern. Betreiber sollten beim Umstieg ein paar Dinge beachten, dieses werden am Ende aufgeführt.
+
+## Datenbank
+Folgende Punkte sollte man wissen und beachten:
+* Die Verbindung von PHP zur Datenbank via PDO wird nun mit charset=utf-8 realisiert.
+* Die Datenbankkodierung wird mittels einer Migration umgestellt. Das Encoding ist utf8mb4, die Collation bei Textspalten utf8mb4_unicode_ci.
+* Index Spalten mit MD5-Hashes als Schlüssel verwenden aus Performancegründen latin1_bin als Kodierung. Bei eigenen Tabellen muss man unbedingt darauf achten, dass dort für MD5-Index-Spalten ebenfalls latin1_bin verwendet wird, da sonst JOIN-Befehle auf Grunde unterschiedlicher Collations fehlschlagen!
+* Spalten, die vorher *_bin waren, sind auf utf8mb4_bin umgestellt.
+* Bei der Migration werden außerdem in allen Textspalten die htmlentities durch ihre UTF-8 Repräsentation ersetzt.
+
+ **In der InnoDB-Konfiguration muss außerdem zusätzlich zu den bisherigen Optionen folgende gesetzt sein:**
+* `innodb_large_prefix=1` (Bis MariaDB 10.2)
+* `innodb_file_formate=Barracuda`
+
+Diese beiden Einstellungen werden von der Migration überprüft.
+
+## Code - Kern und Plugins
+
+Folgende Dinge sind bei der Entwicklung im Kern zu beachten:
+* Alle PHP- und JS-Dateien müssen nun standardmäßig UTF-8 sein.
+* Die String-Funktionen sind ihrer mb_*-Variante zu verwenden
+* `studip_utf8(en|de)decode` existiert nicht mehr und wird auch nicht mehr benötigt
+
+### Plugins
+
+Um Plugins für Stud.IP 4.0 vorzubereiten sind zuerst die gleichen Regeln wie für die Entwicklung im Kern zu beachten.
+
+Das Vorgehen zur Umstellung eines Plugins ist wie folgt:
+
+#### Alle Quelldateien umkodieren auf UTF-8
+Am einfachsten geht das, indem man sich ein Konvertierungsskript mit folgendem Befehl erstellen lässt:
+
+```shell
+find . -name '*.php' -exec printf "iconv -f windows-1252 -t utf-8 {} > {}.new \n mv {}.new {}\n" >> convert_files.sh \;
+```
+
+Dies konvertiert alle PHP-Dateien. Falls nötig, kann man dies für andere Dateitypen wiederholen durch anpassen dieses Befehls.
+
+#### Datenbank
+
+Vorhandene Datenbankinhalte werden normalerweise direkt via der Migration aus dem Kern umgestellt, d.h. eine weitere Migration ist nicht nötig. Bei der Erstellung neuer Tabellen ist zu beachten, dass dort dann das korrekte neue Encoding (utf8mb4) angegeben oder besser ganz weggelassen wird.
+
+
diff --git a/docs/docs/quickstart/visual-style-guide.md b/docs/docs/quickstart/visual-style-guide.md
new file mode 100644
index 0000000..699ffc4
--- /dev/null
+++ b/docs/docs/quickstart/visual-style-guide.md
@@ -0,0 +1,1843 @@
+__Der Stud.IP Styleguide ist stets "work in progress".__
+
+Für aktuelle Designfragen bieten wir vom Styleguide-Team (André, Cornelis, Marcus, Sabine, Marco) bis auf Weiteres jeden Donnerstag um 14.00 eine Design-Sprechstunde per Videokonferenz an. Die Konferenz findet in Skype statt.
+
+# Einleitung
+
+Stud.IP begleitet die Studierenden durch viele Jahre ihres Studiums und ist für die Lehrenden ein ständiger Begleiter bei ihrer täglichen Arbeit. Dieser Umstand verlangt Stud.IP eine gewisse beständige Motivationsfähigkeit ab, dass neue Funktionen sich konsistent und harmonisch in das Gesamtbild einfügen und dass sich ständig wiederholende Arbeitsabläufe möglichst einfach und zeitsparend erledigt werden können.
+Diese und weitere Anforderungen spiegeln sich in der Stud.IP-Design Philosophie wieder, die diesen Style-Guide leiten soll:
+
+Einfache Bedienbarkeit statt großem Funktionsumfang: Weniger ist mehr und daher soll Stud.IP nur jene Funktionen bereitstellen, die dem größten Teil der Nutzer hilft. Es gilt, die Bedürfnisse von 80% der Nutzern zu unterstützen, statt mit den besonderen Anforderungen der restlichen 20% die größere Gruppe zu überfordern.
+Ein gleicher Funktionsumfang für alle Veranstaltungen statt individuelle Anpassung an spezielle Bedürfnisse: Nutzer erhalten so eine verlässliche Umgebung unabhängig vom Einsatz in ganz unterschiedlichen Lehr- und Lernsituationen.
+Verlässlichkeit in der Bedienung: Gleichartige Funktionen sollen stets konsistent umgesetzt werden. Bewährte Muster kommen systemweit zum Einsatz. Neue Ansätze sollen konsequent genutzt werden, so dass die gesamte Software von einer Weiterentwicklung profitiert, auch wenn dieses einen größeren Aufwand bei der Umsetzung bedeutet.
+Behutsame Integration neuer Technologien: Die Nutzung neuer Internet-, Browser- oder Medientechnologien sollte nicht sollte nur verwendet werden, wenn ein Verbesserung für einen großen Teil der Nutzer technisch verfügbar ist. Gleichzeitig muss sichergestellt sein, dass ältere Konfigurationen in angemessenem Maße soweit unterstützt werden, dass eine Bedienbarkeit gewährleistet wird.
+Stud.IP ist ebenso auf mobilen Geräten wie auf Dektop-Rechnern vollständig benutzbar. Dabei gilt jedoch kein reines "mobile first" Prinzip: Mobile Seiten können an bestimmen Bereichen (insbesondere für Administratoren und Administratorinnen) eingeschränkte Funktionalitäten bieten.
+
+## Vier allgemeine Gestaltungsprinzipien für Stud.IP
+
+"...the basic principles of design that appear in every well-designed piece of work."
+> Robin Williams, The Non-Designer's Design Book
+
+### Visuelle Gestaltung
+
+Bezüglich der visuellen Gestaltung von Elementen innerhalb einer Seite gibt es viel zu beachten. Eine einfache Möglichkeit die Nutzbarkeit der jeweiligen Seite schnell zu erhöhen, ist die Beachtung der vier C.R.A.P.-Prinzipien:
+* Kontrast (Contrast)
+* Wiederholung (Repetition)
+* Ausrichtung (Alignment)
+* Nähe (Proximity)
+
+Die im Folgenden vorgestellten Regeln gibt es auch als Poster Attach:studip-design-poster-final.pdf.
+
+### Kontrast (Contrast)
+* Kontrast dient als gestalterisches Mittel, um verschiedene Seitenelemente klar unterscheidbar voneinander abzuheben.
+* Kontrast kann dazu verwendet werden, um auf wichtige Inhalte zu fokussieren.
+* Nicht gleiche Elemente sollten sich deutlich voneinander unterscheiden.
+* Kontrast kann über verschiedene gestalterische Elemente realisiert werden z.B. Schriftart, Farbe, Größen, Formen, Nähe, etc.
+
+#### Negativbeispiele
+* Dateien hochladen in Veranstaltungen - 9 gleichaussehende Button in einer Reihe
+
+### Repetition (Wiederholungen):
+* Wiederholungen schaffen Konsistenzen im System
+* Ein konsistentes Design verbessert die Nutzbarkeit des Systems
+* Konsistenzen können durch den wiederholten Einsatz verschiedenster gestalterischer Elemente erzeugt werden:
+ ** Abstrakt: Menüstrukturen, Funktionsabläufe
+ ** Konkret: Icons, Schriften, Bezeichnungen
+
+#### Beispiele
+* Hauptnavigationsleiste bleibt gleich
+* Footer bleibt gleich
+* Infobox/Sidebar
+
+#### Negativbeispiele
+* verschiedene Suchformularvarianten bei der Personensuche
+
+### Ausrichtung (Alignment):
+* Inhaltselemente sollten nicht willkürlich auf einer Seite platziert, sondern an anderen Elementen horizontal und vertikal ausgerichtet sein.
+ Tipps
+* Text sollte links- oder rechtsbündig ausgerichtet sein, aber nicht beides gleichzeitig auf einer Seite.
+* Abstände sollten gleichmäßig sein.
+* Kein rechts ausgerichteter Text in einer rechten Seitenspalte. Dies erzeugt zu viel Whitespace.
+* Hilfslinien zeichnen, Abweichungen festzustellen
+
+#### Negativbeispiel
+* Assistent zum Anlegen von Veranstaltungen
+
+### Nähe (Proximity)
+* Inhaltselemente, die nah beieinander stehen, erwecken den Eindruck, dass sie zusammen gehören:
+* Verwandte Inhaltselemente sollten daher räumlich nah zueinander gruppiert werden.
+* Zwischen unterschiedliche Inhaltselementen sollte genug Abstand vorhanden sein, weil sonst ein Eindruck von Zusammengehörigkeit erweckt wird.
+* Die Gruppierung der Elemente erhöht die Übersichtlichkeit und Inhalte werden besser strukturiert.
+
+#### Negativbeispiel
+* Gruppenverwaltung - TeilnehmerInnen einer Veranstaltung oder Kalender/Adressbuch - Button zum Hinzufügen zu einer Gruppe ist zu weit weg
+
+### Weiterführende Informationen
+* http://www.userfocus.co.uk/articles/A_CRAP_way_to_improve_usability.html
+* http://www.dailyblogtips.com/crapthe-four-principles-of-sound-design/
+* http://lab.christianmontoya.com/designing-with-crap/designing-with-crap-cc.pdf
+* http://www.colorado.edu/AmStudies/lewis/Design/graprin.htm#summary
+* http://blog.teamtreehouse.com/how-crap-is-your-site-design
+
+# Seitenaufbau
+Jede Seite von Stud.IP ist auf gleiche Art und Weise aufgebaut und enthält folgende Elemente:
+
+Kopfzeile: Einleitende Zeile, die eine systemweite Suche beinhaltet. Wird die Hauptnavigation durch Scrollen aus dem Sichtbereich verschoben, wird diese in kompakter Form in der Kopfzeile aufgenommen. Die Kopfzeile kann vom Betreiber erweitert werden.
+Hauptnavigation: Sie leitet jede Seite ein und ist das feststehende Navigationselement, das die Systembereiche miteinander verbindet. Die Zusammenstellung der Kopfzeile hängt von den globalen Rechten des Benutzers ab. Die Kopfzeile repräsentiert die 1. Navigationsebene.
+Scopes: Scopes verbinden bestimmte Funktionen eines Hauptbereiches, etwa dem Nachrichtensystem oder alle Funktionen innerhalb von Veranstaltungen. Ein Scope besitzt einen Bereich bzw. ein Icon in der Hauptnavigation und verweist auf mehrere Funktionen. Ein Scope repräsentiert bzw. beinhaltet stets die 2. Navigationsebene.
+Sidebar: Diese befindet sich am linken Bildschirmrand und enthält in definierter Form mehrere Widgets, etwa Navigation (der gewählten Funktion im gewählten Scope), Aktionen, Ansichten, Export und ggf. weitere Widgets.
+Navigationswidget: Dieses Widget erscheint stets als erstes Widget und repräsentiert, wenn vorhanden, die 3. Navigationsebene.
+Inhaltsbereich: Hier werden sämtliche Inhalte dargestellt. Ein Inhaltsbereich wird aus Tabellen und ContentBoxen bzw. Eingabefeldern gebildet. Für den Inhaltsbereich existieren feste Elemente, aus denen dieser gestaltet werden muss.
+Fußzeile: Diese enthält weitere Links und Verweise, die analog zur Kopfzeile vom Betreiber erweitert werden kann.
+
+//TODO: Screenshot einer idealtypischen Seite
+
+Die eigentliche Seite setzt sich aus Sidebar und Inhaltsbereich zusammen. Beide Bereiche werden vom einem
+Seitentitel eingeleitet. Im Gegensatz zum Design bis Stud.IP 3.5 bringt der Inhaltsbereich nun keinen eigenen Titel
+(bisher teilweise als h1-Objekt gestaltet) mit.
+
+## Kennzeichen des Seitentitels:
+
+* Der Titel muss namensgleich mit dem Eintrag in der Navigation in der Sidebar sein
+* Bei Veranstaltungen wird automatisch der Name der Veranstaltung mit ausgegeben (gleiches gilt für den Einrichtungsbereich bei gewählter Einrichtung)
+
+## Weitere Vorgaben für den Seitenaufbau:
+
+* Jede Seite enthält zwingend eine Sidebar.
+* Jede Sidebar enthält mindestens ein Schmuckbild.
+* am rechten Rand ist der Zugriff auf die Hilfe (Fragezeichen-Icon) als Abschluss des Seitentitels vorgesehen.
+* Aktionen der Sidebar (zu finden im gleichnamigen Widget) werden in Dialogen ausgeführt.
+
+Weitere Informationen zur Sidebar: siehe Abschnitt [Style#sidebar](Sidebar)
+
+# Navigation
+
+Die Navigation in Stud.IP ist in mehreren Ebenen organisiert. Es ist zu unterscheiden in:
+
+* Hauptnavigation: Die Kopfzeile des Systems. Von hier aus werden komplette Funktionsbereiche erschlossen. Jeder
+ dieser Bereiche entspricht einem der Hauptpunkte in der Sitemap und jeder dieser Bereiche präsentiert sich mit einem eigenen Reitersystem.
+* Scopes: Jeder Hauptbereich (zB. Profil oder Community) bringt einen Scope mit, der die Funktionen eines Bereiches
+ aufnimmt. Ein Scope entspricht jeweils der zweiten Ebene in der Sitemap. Eine Funktion darf nur an einer einzigen
+ Stelle in einem Scope eingehangen werden. Somit hat jede Funktion eine eindeutige Zuordnung zu einem der Hauptbereiche.
+* Navigation in der Sidebar: Verschiedene Aufgaben innerhalb einer Funktion finden sich im Navigationsbereich der
+ Sidebar. Diese führen an dieser Stelle zu einem neuen Seitenaufruf (im Gegensatz zu Aktionen in der Sidebar).
+* Aus der Navigation der Sidebar sind auch Links in andere Hauptbereiche möglich, sollten jedoch vermieden werden.
+ Im Idealfall bleibt auch die Navigation einer Funktion innerhalb ihrer eigenen Aufgaben bzw. innerhalb des
+ jeweiligen Scopes. Ein Eintrag in der Navigation der Sidebar entspricht der dritten Ebene in der Navigation.
+
+Weitere Hinweise zum Aufbau der Sidebar und ihrer unterschiedlichen Widgets findet sich im entsprechenden [Bereich
+des Styleguides](#Sidebar)
+
+## Kopfzeile
+
+Die Kopfzeile leitet jede Seite ein und bietet Zugriff auf alle Kernbestandteile von Stud.IP:
+
+Mini:kopfzeile.png
+
+Je nach Rechtestufe des angemeldeten Nutzers und eingerichteten Systemplugins werden unterschiedliche Systembereiche von hier aus zugänglich gemacht.
+
+Die Kopfzeile sieht den größten Gestaltungsspielraum für Anpassungen an die Corporate Identity des Betreibers vor.
+
+Folgende Anpassungen sind hier möglich:
+* Einfügen des eigenen Logos an beliebiger Position (Vorschlag: Rechts neben dem Stud.IP Logo)
+* Einfügen weiterer eigener Links in der Kopfzeile (Vorschlag: links neben der globalen Suchen)
+
+Noch einige Hinweise zur Eigenanpassung der Kopfzeile:
+* Entfernen Sie nicht die Icons aus der Kopfzeile, da die Icons ihre Gestaltung innerhalb des Systems wiederholt
+auftauchen und damit eine Verbindung zu dieser Navigation schaffen
+* Entfernen Sie nicht die Beschriftung der Icons, da die Nutzer über diese Beschriftung wichtige Erklärungen erhalten
+ und der Text auch in anderen Systemsprachen zur Verfügung steht.
+* Ändern Sie nicht die Reihenfolge der Icons oder teilen Sie die Icons in mehrere Zeilen auf.
+* Ordnen Sie Kopfzeile nicht an andere Stellen (etwa als Seitenleiste) an. Das Stud.IP-System benötigt an einigen
+ Stellen teilweise eine sehr breite Darstellung. Die Kopfzeile ist in dieser Form am besten auf das System abgestimmt.
+
+## Reite (Scopes)
+Scopes fassen die Funktion eines Hauptbereiches (etwa alle Funktionen innerhalb einer Veranstaltung oder innerhalb des Nachrichtensystems) zusammen.
+
+Mini:style_reiter.jpg
+
+Stud.IP ergänzt in einem Scope (ebenso wie in der Hauptnavigation) automatisch einen "Überlauf", der in einem
+Drop-Down-Menü alle Icons aufnimmt, die nicht mehr in die horizontale Darstellung (je nach Bildschirmbreite) passen
+würde. Grundsätzlich sollte beim Entwerfen neuer Funktionen darauf achten, möglichst knappe Bezeichnungen zu wählen,
+sodass möglichst viele Funktionen nebeneinander Platz finden. Die Breite der jeweiligen Beschriftungen bedingt die Breite des Scopes!
+
+## Sidebar
+
+### Vorbemerkung
+
+Das Konzept der Infoboxen (Stud.IP-Versionen bis 3.0) hat sich grundlegend geändert zum Sidebar-Konzept (ab Stud.IP
+3.1), das viele der Funktionen aus den alten Infoboxen aufnimmt, jedoch nicht direkt ersetzt. In Rahmen dieser
+Umstellung wurde die 3. Navigationsebene als Zeile unterhalb der Reiter in ein Navigationswidget der Sidebar verlegt.
+
+Attach:Style/Sidebar-dafault.jpg
+
+### Kurzbeschreibung
+Die Sidebar befindet sich an fester Position am linken Rand einer Stud.IP-Seite. Die Sidebar ersetzt die Infobox älterer Stud.IP-Versionen und enthält mindestens eins, meistens mehrere Widgets. In der Sidebar befinden sich innerhalb von diesen Widgets die Elemente der 3. Navigationsebene, Aktionen, Ansichtsoptionen, seiteninterne Suchmöglichkeiten und Exportfunktionen. Sofern diese Standardwidgets nicht passend sind, kann eine Seite weitere Widgets haben.
+Die Sidebar besitzt zudem Orientierungsbild im Kopfbereich, das den Namen der Seite enthält, das Baisisicon des jeweiligen Bereiches zeigt und einen Avatar aufnehmen kann.
+Jede Seite sollte eine Sidebar besitzen.
+
+### Aufbau & Elemente
+
+#### Orientierungsbild
+Das Orientierungsbild ist 520px breit und 200px hoch. Zu allen Basisfunktionen (bzw. aufbauend auf deren Icons) werden entsprechende Orientierungsbilder ausgeliefert. Grundsätzlich können Standorte diese Bilder tauschen, sollten aber darauf achten, dass Bildinhalt und Helligkeit zum umgebenden Design passen. Im Zweifel steht die Stud.IP-GUI-Gruppe bereit, weitere Bilder zu erstellen oder Tipps zu geben, wie man eigenen Bilder integrieren kann.
+
+#### Typen von Widgets
+| Typ | Beschreibung |
+| ---- | ---- |
+| Navigation | Enthält automatisch die 3. Navigationsbene entsprechend der Stud.IP-Navigationsstruktur (ehemals 3. Navigationsebene unterhalb der Reiterleiste). Navigationspunkte springen auf andere Seiten aber bleiben idealerweise innerhalb eines Navigationskontextes (=Reitersystem). Die aktuell gewählte Seite wird mit einem blauen Pfeil markiert. Navigationspunkte zeigen keine Icons. |
+| Aktionen | Enthält Aktionen, die den Inhalt der aktuellen Seite beeinflussen. Aktionen öffnen grundsätzlich einen Dialog und verlassen somit nicht den aktuellen View, den der Nutzer sieht. |
+| Ansichten | Diese enthalten Ansichtsoptionen bzw. Filter, die den angezeigten Content auf der jeweiligen Seite einschränken. Die jeweils gewählte Ansicht bzw. der Filter ist mit einem gelben Pfeil markiert. |
+| Suche | Ein Such-Widget ist seitenspezifisch, ermöglicht also das Suchen innerhalb des Contents der Seite. Idealerweise gilt, dass eine Suche hier nur innerhalb des Contents filtert, den ich auf dieser Seite insgesamt sehen kann bzw. erreichen kann. Wenn der Content einer Seite selbst ein Suchergebnis liefert (z.B. bei allen Suchfunktionen in Stud.IP) muss diese Suche außerhalb der Sidebar, z.B. in einer Content-Box im Content-Bereich der Seite realisiert werden. Ein Suchwidget könnte dann theoretisch den gefunden Content dynamisch Einschränken, idealerweise ohne Reload der Seite |
+| Export | Hier werden alle Funktionen aufgenommen, die konkret eine Datei (z.B. PDF, XLS-Export, CSV-Datei) zum Download anbieten. |
+
+Grundsätzlich beginnen Seiten mit der Navigation und den Aktionen, dann folgende weitere Widgets (in der Regel Suche, Ansichten oder Export). Die weitere Widgets können je nach Nutzungshäufigkeit der jeweiligen Seite platziert werden, die ersten beiden Positionen sind in der Reihenfolge fest vorgegeben.
+
+
+#### Weitere Typen von Widgets
+
+Gelegentlich tauchen folgende Type auf:
+
+| Type | Beschreibung |
+| ---- | ---- |
+| Einstellungen | Für Einstellungen, die sich direkt auf die Seite auswirken und schnell in der Sidebar vorgenommen werden sollen |
+| Merkliste | Für das Zwischenspeichern von beliebigen Objekten |
+
+
+### Was nicht in die Sidebar gehört
+
+* Hilfetexte: Bisher oft in der Infobox verwendet, gehören erklärende oder einleitende Texte über die Funktion einer Seite nicht mehr in die Sidebar. Der beste Platz dafür ist die in der Version 3.1 neu geschaffene Hilfe-Lasche, in der auch Touren gestartet werden und der Link zum Hilfe-Wiki zu finden ist.
+* Formulare: Mit Ausnahme eines Eingabefeldes für das Such-Widget gehören Formulare nicht in die Sidebar.
+
+### Sonst noch zu beachten
+
+* Für die Sidebar gibt es eine feststehende API, die für die Erstellung verwendet werden muss.
+* Die Umstellung des Admin-Bereiches erfolgt voraussichtlich im Rahmen der Arbeiten der Version 3.2, bis dahin ist nur die Navigation in das entsprechende Widget verlegt.
+* Außer im Navigationswidget sollten in der Sidebar eindeutige und passende Icons in der Farbe blau verwendet werden und klickbar sein. Insbesondere Aktionen profitieren von der leichten Auffindbarkeit durch Icon + Text.
+
+## Inhaltsbereich
+
+Der Inhaltsbereich umfasst alle jene Inhalte, die von der jeweiligen Funktion angezeigt oder bearbeitet werden.
+
+In diesem Bereich finden alle Objektmanipulationen und die Inhaltsanzeige statt. Entscheidend ist, dass in diesem Bereich eigentlich nur Objekte (die als solche gekennzeichnet sind, siehe später), sie manipulierende Methoden und verschiedene weitere (Meta-)Informationen zu diesen Objekten platziert werden sollten. Erklärungstexte, verweise auf andere Systemteile und andere Navigationselemente dürfen nicht in diesem Bereich erscheinen.
+
+Für die Gestaltung sollten standardisierte grafische Elemente verwendet werden, Funktionen, die bereits in ähnlicher Weise im System vorhanden sind, müssen sich in der Bedienung daran anlehnen. Gerade im Inhaltsbereich muss es das erklärte Ziel sein, mit bekannten Elementen zu arbeiten, um dem Nutzer eine vertraute Umgebung &#8211; auch bei neuen Funktionen &#8211; zu bieten.
+
+Einige grundsätzliche Hinweise zur Gestaltung des Inhaltsbereiches:
+* Vermeiden Sie, im Inhaltsbereich der Seiten Texte frei zu platzieren. Es gibt eine Reihe von grafischen Gestaltungsmöglichkeiten, die im folgenden beschrieben werden, mit denen Sie jedwede Inhalte innerhalb des Inhaltsbereiches markieren und jeweils von anderen Objekten abgrenzen können.
+
+
+## Inhaltselemente
+
+Generell gilt: Alle Elemente im Inhaltsbereich müssen durch passende Objekte eingefasst werden. Meistens sind dies Content-Boxen (bzw. Fieldareas in Formularen) oder Tabellen. Texte und Eingabemöglichkeiten dürfen nicht frei auf dem (weißen) Hintergrund gesetzt werden.
+
+### Text
+Fließtext sollte in Stud.IP durch die Verwendung semantisch entsprechender HTML Attribute strukturiert werden. Dies gilt auch für die Formatierung des Textbildes. Die inhaltliche und logische Struktur des Textes wird somit auf den Quellcode übertragen. Dadurch wird der Text nicht nur lesbarer für den Entwickler, sondern auch zugänglicher für Screenreader.
+
+#### Übersicht der HTML Markups
+
+##### Überschriften
+Überschriften werden seit Stud.IP 4.0 im Inhaltsbereich nicht mehr verwendet. Entsprechende Auszeichnungen von Überschriften dürfen nur noch im Content der jeweiligen Funktion (etwa Wiki-Texte, Informationsseite oder Foren-Beiträge) verwendet werden, dienen aber nicht mehr der Gliederung oder Beschreibung des Inhaltsbereiches.
+
+##### Einfache Listen und Aufzählungen
+Um einfache Listen in Stud.IP darzustellen wird das `<ul>` - Markup verwendet.
+Entsprechende Listenelemente werden mittels `<li>` eingefügt. Auch für Listen gilt, dass diese durch gliedernde Elemente (in der Regel Content-Boxen) eingefasst werden.
+
+Beispiel:
+
+```html
+<ul>
+<li> Eintrag 1</li>
+<li> Eintrag 2</li>
+...
+<li> Eintrag N</li>
+</ul>
+```
+
+Weitere Elemente werden in den entsprechende Bereichen dieses Styleguides genauer beschrieben:
+
+##### Tabellen
+##### Formulare
+##### Content-Boxen
+##### Suchen
+
+
+# Design
+## Farben und Farbraum
+Farben sind mit das wichtigste Gestaltungsmittel. Richtig eingesetzt, können Farben Anwendern helfen Aufgaben leichter durchzuführen.
+
+Die Standard-Farben des aktuellen Stud.IP Designs wurden von den Core-Group
+GUI-Verantwortlichen in Zusammenarbeit mit einem Designer festgelegt.
+
+Das Farbschema baut auf einigen Grundfarben und festen Kontrastabständen auf.
+
+Prinzipiell kann jede Stud.IP Installation durch Anpassen der CSS-Dateien farblich verändert werden. Zu beachten ist, dass jedoch nur die base-color angepasst werden sollte. Dadurch verändern sich andere Farben entsprechend den vorgegeben Farbwerten.
+(Dies setzt allerdings voraus, dass die Farben in den less-Dateien angepasst werden und mit dem less-Compiler kompiliert werden. Auch ist zu beachten, dass die vollständige Implementierung der Stud.IP-CSS-Dateien erst in Zukünftigen Versionen umgesetzt sein wird).
+Von allen Basisfarben sind in dem Farbklima Abschwächungen in 20% Schritten vorgesehen, die automatisch über den less-Compiler erzeugt werden.
+
+### Bedeutung und Auswahl der Farben in Stud.IP
+
+Farben sind mit Bedacht zu wählen, da diese wie optische Methaphern wirken und Emotionen ansprechen. Daher ist es wichtig, Farben konsistent zu verwenden. Bislang werden Farben in Stud.IP wie folgt eingesetzt:
+
+![image](../assets/476d123391bbc2e4a825a1ea146a8465/image.png)
+
+PDF Download: [170804_Studip-Farbset.pdf](../assets/6d14189aa9093eb042bfa56eae8c7dc2/170804_Studip-Farbset.pdf)
+
+#### Blau (base-color und content-color)
+Blau ist in verschiedenen Abstufungen die Standard-Hintergrundfarbe für das aktuelle Stud.IP Theme.
+Die Basisfarbe ist #28497c. Zur Hinterlegung von Content (also Inhalte innerhalb des Blatt Designs) wird der Farbwert #899ab9 als Basis genommen.
+
+Blau wird zusätzlich für klickbare Objekte verwendet, d. h. mit blau werden Text-Links und klickbare Icons gekennzeichnet.
+
+Blau ist auch die Hintergrundfarbe für [Messageboxen](MessageBox) mit Informationsmeldungen.
+
+Die base-color kann von Betreiber angepasst werden an eigene Farben, zB. um dem eigenen CD zu entsprechen. Die content-color sollte nicht angepasst werden.
+
+#### Grau (light-gray, dark-gray)
+
+Zur Hinterlegung verschiedener Bereiche (zB. Infoboxen, Navigation) existieren unterschiedliche Grautöne, die frei verwendet werden dürfen. Die Basiswerte sind #69767f (light-gray) und #3c454e (dark-gray).
+Inhalte (also Tabellen, Textbeiträge, Nachrichten, Formulare) dürfen nur mit der content-color (blau) hinterlegt werden. Andere Objekte können auch mit Grau hinterlegt werden.
+
+### Markierungsfarben
+
+Neben den Grundfarben werden Farben auch für unterschiedliche Markierungen/Kategorisierungen verwendet. Die dafür erlaubten Farben sind ebenfalls definiert:
+
+![image](../assets/1cde32e97e840ad35cd7840e4d61b016/image.png)
+
+#### Rot
+Rot wird als Signalfarbe an mehreren Stellen eingesetzt:
+
+Rot kennzeichnet zum einen kritische Aktionen und wird somit beispielsweise als Rahmenfarbe für Fehlermeldungen verwendet. Auch das Icon in einer Fehlermeldung ist rot gefärbt.
+
+Zum anderen wird alles Neue (aus Sicht des jeweiligen Nutzes) in Rot hervorgehoben. So kommen rote Icons beispielsweise auf der Seite "Meine Veranstaltungen" zum Einsatz, wenn einer der Bereiche einer Veranstaltung für den Nutzer neue Inhalte enthält. Auch in den Bereichen der Veranstaltungen gibt es an mehreren Stellen rote Markierung für neue Beiträge.
+
+Basisfarbton für rot ist: `#d60000`
+
+#### Grün
+Grün wird lediglich für positive Rückmeldungen verwendet. Grün ist z. B. die Rahmenfarbe Meldungen mit Erfolgsbestätigung.
+
+In der Gestaltung von Stud.IP.Inhalten oder anderen Elementen darf grün nicht eingesetzt werden!
+
+#### Gelb (activity-indikator)
+Gelb wird lediglich als Markierungsfarbe genutzt.
+
+Beispielsweise ist der Indikator, welche Ansicht in einer Seite mit mehreren Ansicht gewählt wurde, ein gelber Pfeil.
+
+Im Forum oder dem Wiki markiert die Farbe Gelb Fundstellen in der Trefferliste.
+
+Gelbe Verschiebepfeile zum Umsortieren von Objekten sind in der aktuellen Gestaltung nicht mehr zulässig.
+
+Basisfarbe ist `#ffbd33`
+
+#### Schwarz und Weiß
+Schwarz und Weiß werden als Schrift- und Kontrastfarbe verwendet. Schrift und Symbole werden je nach Hintergrund schwarz oder weiß gezeichnet.
+
+#### Hinweise
+Die hexadezimalen Werte der Farben sind in LESS-Dateien (`public/assets/stylesheets/mixins/colors.less`) definiert und müssen bei Anpassungen mit einem entsprechenden Less-Compiler in die Stylesheets übertragen werden.
+Das händische Anpassen einzelner Farbwerke in den CSS-Dateien wird ausdrücklich nicht empfehlen, da einige Farben auch in Abhängigkeiten zueinander (etwa von der Base-color) definiert/erzeugt werden.
+
+Die Verwendung von Farben für Icons wird im Abschnitt zu [Icons](Visual-Style-Guide#Icons) ausführlicher beschrieben.
+
+### Allgemeine Hinweise zur Farbauswahl
+
+#### Farben mit Bedacht und sparsam verwenden
+Farben sollten sparsam verwendet werden. Um Bereiche im [Inhaltsbereich einer Stud.IP Seite](http://hilfe.studip.de/develop/Style/DesignSeitenlayout) durch Farbe zu kennzeichnen, empfiehlt es sich laut ISO 9241-12 nicht mehr als sechs (zusätzlich zu schwarz und weiß) verschiedene Farben zu verwenden. Die verwendeten Farben sollten durch den Anwender gut unterscheidbar sein.
+
+#### Farbe nicht als alleiniges visuelles Hilfsmittel verwenden
+Farben sollten nicht als einziges visuelles Mittel verwendet werden, um Informationen zu vermitteln oder Elemente zu kennzeichnen. Für farbenfehlsichtige Nutzer ist es möglicherweise schwierig zwei Objekte zu unterscheiden, die sich nur in ihrer Farbe unterscheiden. Unterschiede sollten zusätzlich durch z. B. unterschiedliche Formen, Positionen oder eine textuelle Beschreibung gekennzeichnet werden.
+
+#### Farbtöne mit gleichen Sättigungsgrad verwenden
+Um eine harmonisches Farbdesign zu erreichen, sollten Farbtöne verwendet werden, die den gleichen Sättigungsgrad aufweisen. Sättigung (bzw. Buntheit) bezeichnet den Grauanteil einer Farbe. Je weniger Grau eine Farbe enthält, desto leuchtender wirkt sie.
+
+Große Flächen sollten nicht in leuchtenden (gesättigten) Farben gestaltet werden. Diese werden schwer lesbar und können mitunter Kopfschmerzen verursachen.
+
+#### Ist der Kontrast zwischen Elementen und ihrem Hintergrund ausreichend?
+Wenn sich der Farbton von Vorder- und Hintergrundfarben zu sehr ähnelt, sind Unterschiede schwer erkennbar.
+
+Tipps zur Überprüfung des Kontrastes einer Farbkombination:
+* Um zu überprüfen, ob ein ausreichender Kontrast vorhanden ist, empfiehlt es sich die Seite schwarz-weiß zu drucken. Wenn der Ausdruck gut lesbar ist, ist typischerweise ein ausreichender Kontrast vorhanden.
+* Mit dem Online-Tool [Color Contrast Checker](http://www.snook.ca/technical/colour_contrast/colour.html) kann direkt überprüft werden, ob ein ausreichender Kontrast zwischen zwei Farben vorhanden ist.
+* Auf manchen Betriebsystemen kann auch die Darstellung bereits als Grundeinstellung in Graustufen geschaltet werden, um die Kontraste zu testen.
+
+#### Unruhige und ablenkende Hintergründe vermeiden
+Ungünstig sind Muster oder Bilder im Hintergrund, die einen ungleichmäßigen Kontrast verursachen, das Auge vom Text ablenken und damit die Lesbarkeit erschweren.
+
+### Weiterführende Links
+* Tutorial: Farben im Webdesign [http://metacolor.de/](http://metacolor.de/)
+* [Farb/Kontrastanalysen mit Bezug auf a11y-Kriterien](http://www.blog.mediaprojekte.de/grafik-design/farb-kontrast-analyse-die-accessibility-der-farben-testen)
+* http://e-campus.uibk.ac.at/planet-et-fix/M8/8.5.2_Praesentationen/links/farben.html
+
+
+
+# Icons
+
+Seit der Version 2.0 bringt Stud.IP ein einheitliches Icon-Set mit. Das Set zeichnet sich durch eine klare und einheitliche Gestaltung, nach Aufgaben und Einsatzbereichen getrennte Farb-Sets, barrierearme Bedienung durch klare Formen/optische Zusätze für bestimme Aufgaben und eine Reihe weiterer Merkmale, die die Stud.IP-Formensprache vereinheitlichen, aus. Des weiteren liegen alle Icons bereits als Vektorgrafiken vor, so dass eine Verwendung in anderen Kontexten (zB. Print) und eine Veränderung der Icon-Größe in Stud.IP (etwa für eine verbesserte Touch-Bedienung) zukünftig möglich ist.
+Das Iconset findet grundsätzlich für alle grafischen Schaltflächen, Markierungen, Objekt-Darstellungen usw. Anwendung. Als einzige Ausnahme davon existieren in Stud.IP weiterhin die Textbuttons und einige wenige Sonderformen oder nicht-iconartige Grafiklelemente (zB. Linien oder Trenner).
+
+
+#### Gestaltung
+
+Die Icon-Landschaft wurde deutlich entschlackt, moderner und effizienter gestaltet. Skalierbarkeit und eine einfache, klare Formensprache standen bei der Entwicklung im Mittelpunkt. Das neue Iconset ist funktional und selbsterklärend. So wird die Übersichtlichkeit der Funktionen und die intuitive Bedienung gefördert. Es gibt nun für alle Größen und Einsatzgebiete eine einheitliche Optik der neuen Icons. Die Verwendung von festen Grundformen zusammen mit förmlich und farblich abgegrenzten Zusätzen, die Funktionen und Zustände markieren, gewährleistet eine barrierearme Nutzbarkeit bei hohem Wiedererkennungseffekt.
+
+Icons in Studip 2.0 sind nach einem festgelegten Raster gestaltet und minimalistisch in Farbe und Form. Sie sind grundsätzlich monochrom in festgelegten Farben gehalten. Linienstärke und Farbfüllungen werden so eingesetzt, dass die Icons ein einheitliches optisches Gewicht erhalten. Diese Regeln sollen künftig auf alle in Stud.IP verwendeten Icons übertragen werden.
+
+Sicher gibt es zusätzlich zur normalen Projektentwicklung viele Plugins und Erweiterungen, bei denen einen Bedarf gibt, bestehende Icons anzupassen oder neue zu erstellen. Die dafür notwendigen Arbeiten übernimmt der Stud.IP e.V., der für die Entwicklung in Budget in gewissem Umfang bereithält. Koordiniert die Entwicklung über den Vorstand des Stud.IP e.V.
+
+#### Icon-Rollen
+
+Ab der Version 3.4 werden Icons über die Icon-API angesprochen. Bisher wurden Icons wie Dateinamen eingebunden. Damit war die verwendete Farbe hartkodiert. Wenn in Stud.IP-Installationen Anpassungen an das Farbschema der Hochschule vorgenommen wurden, musste daher der Code geändert bzw. unschöne "Hacks" vorgenommen werden.
+
+Ab Stud.IP v3.4 werden Icons nicht mehr mit ihrer Farbe referenziert, sondern mit der Rolle, die sie übernehmen wollen.
+Ein Beispiel: Bisher wurden alle Icons, die als bzw. in einem Link angezeigt wurden, im Code mit der Farbe Blau hartkodiert: `Assets::img('icons/blue/seminar')` Wollte eine Hochschule alle Link-artigen Icons lieber in der Farbe
+Grün darstellen, mussten dafür grüne Icons in das Verzeichnis "blue" gelegt werden (Was aber auch nicht immer funktioniert,
+wenn z.B. die Hintergrundfarbe dann dieselbe wie die Icon-Farbe ist.) oder alle entsprechenden Vorkommnisse von `blue` im Code durch `green` ersetzen.
+
+Mit der neuen Icon-API wird nun die Rolle hartkodiert. Die globale Zuordnung von Rollen zu Farben übernimmt dann die entsprechende Übersetzung.
+
+#### Rollen
+
+Derzeit (Stud.IP v3.4) findet man die Zuordnung von Rollen zu Farben
+in der Klasse "Icon" (`lib/classes/Icon.class.php`):
+
+| Rolle | Farbe | Bedeutung |
+| ---- | ---- | ---- |
+| `Icon::ROLE_INFO` | black | Alte Beschreibung: *Diese Icons werden ausschließlich in den Infoboxen verwendet und sind nie klickbar. Sie erläutern grafisch die Aktionen, die die Infobox anbietet. So können hier Aktionen mit dem zugehörigen Icon gezeigt werden, Informationen mit einem "I" illustriert werden oder Verweise auf andere Systembereiche mit den zu diesen Bereichen passenden Icons ergänzt werden.* |
+| `Icon::ROLE_CLICKABLE` | %blue%blue | Alte Beschreibung: *Die blauen Icons sind der Standard für alle klickbaren Icons, unabhängig davon, in welchem Kontext sie verwendet werden (Ausnahme: Übersicht "Meine Veranstaltungen"). Insbesondere freistehende Aktionen sollten immer neben dem Linktext ein solches Icon zeigen. * |
+| `Icon::ROLE_ACCEPT` | %green%green | Alte Beschreibung: *Grün kommt nur in dem Fall, dass der Bestätigungs-Haken gezeigt wird, zum Einsatz.* |
+| `Icon::ROLE_STATUS_GREEN` | %green%green | Alte Beschreibung: *Grün kommt nur in dem Fall, dass der Bestätigungs-Haken gezeigt wird, zum Einsatz.* |
+| `Icon::ROLE_INACTIVE` | %grey%grey | Alte Beschreibung: *Diese Variante kommt zum Einsatz, wenn Icons nicht klickbar sind und nicht innerhalb von Infoboxen eingesetzt werden. Sie drücken auch oft einen Status aus und werden für alle Objekte wie News, Votings oder Dateien verwendet, um ein solches Objekt zu klassifizieren. Eine Ausnahme bildet "Meine Veranstaltungen". Auch hier drücken die grauen Icons einen Zustand aus ("nur bekannte Objekte eines Types") aber können trotzdem geklickt werden, da von hier aus auch die jeweiligen Bereiche direkt angesprungen werden können. Dieser Sonderfall gilt jedoch nur für "Meine Veranstaltungen"* |
+| `Icon::ROLE_NAVIGATION` | %ltblue%lightblue | |
+| `Icon::ROLE_NEW` | red | Alte Beschreibung: *Die Farbe Rot dient dazu, Neues darzustellen oder hervorzuheben. Rote Icons kommen auf "Meine Veranstaltungen" zum Einsatz, wenn einer der Bereiche einer Veranstaltung für den Nutzer neue Inhalte führt. Aus Gründen der barrierearmen Bedienung reicht die rote Färbung allein nicht aus, es muss immer auch der Zusatz "neu" verwenden werden, um dem Icon auch eine eindeutige Form zu geben, es sei denn, die Kombination von Farbe und Form des Icons selbst ist eindeutig (etwa ein rotes X).* |
+| `Icon::ROLE_ATTENTION` | red | Alte Beschreibung: *Die Farbe Rot dient dazu, Neues darzustellen oder hervorzuheben. Rote Icons kommen auf "Meine Veranstaltungen" zum Einsatz, wenn einer der Bereiche einer Veranstaltung für den Nutzer neue Inhalte führt. Aus Gründen der barrierearmen Bedienung reicht die rote Färbung allein nicht aus, es muss immer auch der Zusatz "neu" verwenden werden, um dem Icon auch eine eindeutige Form zu geben, es sei denn, die Kombination von Farbe und Form des Icons selbst ist eindeutig (etwa ein rotes X).* |
+| `Icon::ROLE_STATUS_RED` | red | Alte Beschreibung: *Die Farbe Rot dient dazu, Neues darzustellen oder hervorzuheben. Rote Icons kommen auf "Meine Veranstaltungen" zum Einsatz, wenn einer der Bereiche einer Veranstaltung für den Nutzer neue Inhalte führt. Aus Gründen der barrierearmen Bedienung reicht die rote Färbung allein nicht aus, es muss immer auch der Zusatz "neu" verwenden werden, um dem Icon auch eine eindeutige Form zu geben, es sei denn, die Kombination von Farbe und Form des Icons selbst ist eindeutig (etwa ein rotes X).* |
+| `Icon::ROLE_INFO_ALT` | %bgcolor=black white%white | Alte Beschreibung: *Die weiße Variante wird immer dann verwendet, wenn Icons innerhalb einer dunklen Umgebung (in der Regel die Kopfzeile von frei schwebenden Fenstern mit dunkelblauer Zeile) vewendet werden. Auch graue Tabellenköpfe erhalten ggf. weiße Icons. In der Regel sind diese nicht klickbar. Das weiße Icon kann auf weißem Hintergrund nicht gesehen werden.* |
+| `Icon::ROLE_SORT` | %bgcolor=black yellow%yellow | Alte Beschreibung: *Gelbe Icons werden ausschließlich zum Verschieben von Objekten benutzt. Daher existieren in diesem Set auch nur Dreiecke und Pfeile in allen Varianten.* |
+| `Icon::ROLE_STATUS_YELLOW` | %bgcolor=black yellow%yellow | Alte Beschreibung: *Gelbe Icons werden ausschließlich zum Verschieben von Objekten benutzt. Daher existieren in diesem Set auch nur Dreiecke und Pfeile in allen Varianten.* |
+
+
+#### Zusätze
+
+Es existieren eine Reihe von grafischen Zusätzen, die vielfältig eingesetzt werden können, um Aktionen, die hinter Icons liegen oder auch Zustände zu visualisieren. In der Regel werden Zusätze immer in rot dargestellt. Eine Ausnahme bildet der gelbe Zusatz „Verschieben“.
+
+Zusätze an Icons werden ab Stud.IP v3.4 über die Icon-API referenziert. Möchte man zB ein `seminar`-Icon als Link mit dem Zusatz `add` (also Hinzufügen) einbauen: `Icon::create('seminar+add')`
+
+| Status | Bild |Beschreibung
+| ---- | ---- | ---- |
+| `accept` | ![](https://develop.studip.de/studip/assets/images/icons/blue/accept/seminar.svg) | **Akzeptieren**: Der Haken drückt aus, dass hier eine Bestätigung im Kontext des Objekts dargestellt wird. |
+| `add` | ![](https://develop.studip.de/studip/assets/images/icons/blue/add/seminar.svg) | **Hinzufügen**: Das Plus-Zeichen drückt aus, dass hier ein neues Objekt erzeugt werden kann. Das Anlegen eines Objektes oder der Sprung auf einen Bereich, in dem das Anlegen möglich ist, wird mit diesem Zusatz belegt. Er kann an blauen Icons mit Klick oder schwarzen und grauen Icons verwendet werden.|
+| `decline` | ![](http://develop.studip.de/studip/assets/images/icons/blue/icons/grey/decline/trash.svg) | **Aktion gesperrt**: Zuweilen werden Aktionen als "nicht möglich" dargestellt. In diesem Fall erhalten die Aktions-Icons ein X als Zusatz. |
+| `edit` | ![](https://develop.studip.de/studip/assets/images/icons/blue/edit/seminar.svg) | **Bearbeiten**: Der Stift an einem Objekt drückt aus, das mit einem Klick auf dies Icon ein Objekt bearbeitet werden kann. |
+| `export` | ![](https://develop.studip.de/studip/assets/images/icons/blue/export/seminar.svg) | **Exportieren**: Über dieses Icon werden ein oder mehrere Objekte des entsprechenden Types exportiert (z. B. als CSV-Datei)|
+| `move_right` | ![](https://develop.studip.de/studip/assets/images/icons/blue/move_right/mail.svg) | **Verschieben**: Um ein Objekt zu verschieben, gibt es den Zusatz eines Pfeils. Bis zur Version 2.4 waren diese Zusätze gelb, seit der Version 2.5 sind alle Zusätze in rot zu halten.
+| `new` | ![](http://develop.studip.de/studip/assets/images/icons/blue/icons/red/new/seminar.svg) | **Neu**: Das Sternchen drückt einen neuen Inhalt aus. Das Sternchen wird (außer in der Kopfzeile) mit einem roten Icon kombiniert.|
+| `remove` | ![](https://develop.studip.de/studip/assets/images/icons/blue/remove/seminar.svg) | **Löschen/Entfernen**: Mit einem Minus wird die Möglichkeit, das entsprechende Objekt zu löschen, markiert.|
+| `visibility-visible` | ![](https://develop.studip.de/studip/assets/images/icons/blue/visibility-visible/seminar.svg) | **Sichtbar**: Ein Objekt wird bei Klick auf dieses Icon sichtbar geschaltet. |
+| `visibility-invisible` | ![](https://develop.studip.de/studip/assets/images/icons/blue/visibility-invisible/seminar.svg) | **Unsichtbar**: Ein Objekt wird bei Klick auf dieses Icon unsichtbar geschaltet. |
+
+
+Das Icon-Set enthält im Bausatz noch weitere Zusätze, die aktuell jedoch nicht verwendet werden. So finden sich Pfeile in alle vier Richtungen, Pause-Zeichen, Bestätigen-Zeichen (als Gegenstück zum "X"), Minus-Zeichen (als Gegenstück zum Hinzufügen) und Aktualisieren.
+
+#### Größen
+
+Icon-Größen können über die Render-Methoden der Icon-API angegeben werden. Inzwischen werden Icons als frei skalierbare SVG ausgeliefert.
+Seit der Version 5.0 wird die Größe im Icon nicht mehr mitgegeben (vormals 16 * 16 Pixel, außer anders angegeben).
+
+#### Verzeichnisstruktur
+
+War früher die modulare Verzeichnisstruktur wichtig, verbirgt die Icon-Klasse nun diese Implementationsdetails. Bei Verwendung der Icon-API kommt man damit nicht mehr in Berührung.
+
+Historisch lagen die Icons in etwa dieser Verzeichnisstruktur:
+`icons/<Größe>/<Farbe>/<Zuatz>/<Name des Icons>.png`
+
+#### Liste der verfügbaren Icons
+
+##### Stammicons
+
+Für alle in Stud.IP vorhandenen Objekte existieren Stammicons, von denen alle weiteren Formen oder Varianten logisch abgeleitet werden. Gegenwärtig existieren folgende Stammicons:
+
+(In vielen Fällen existieren sowohl gefüllte und transparente bzw. invertierte Varianten zu einem Stammicon. Hier ist in der Regel die normale Version und nicht die Alternative einzusetzen.)
+
+| Bild | Lizenz | Beschreibung
+| ---- | ---- | ---- |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/60a.svg) | 60a | **Lizenz nach §60a** Dokument-Weitergabe im Rahmen der §60a Lizenz (aktuell)|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/cc.svg) | CC | **Lizenz nach CC** Dokument-Weitergabe im Rahmen von CC |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/license.svg) | license | **Lizenz allgemein** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/own-license.svg) | own-license | **Eigene Lizenz** Dokument-Weitergabe im Rahmen einer eigenen Lizenz/selbst erstellt |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/public-domain.svg) | public-domain | **Freie Lizenz** Dokument-Weitergabe im Rahmen einer freien Lizenz|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/accept.svg) | accept | **Akzeptieren/akzeptiert** Dieses Symbol ist die Grundform für positive Rückmeldungen an den Nutzer. |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/action.svg) | action | **Aktionsmenu** Icon zur Initiierung bzw. Verankerung des Aktionsmenu|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/activity.svg) | activity | **Activity-Stream** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/no-activity.svg) | no-activity | **keine Aktivität im Activity-Stream** leerer Activity-Stream |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/add.svg) | add | **Hinzufügen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/add-circle.svg) | add-circle | **Hinzufügen** für Popover |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/admin.svg) | admin | **Administration** Alle Administrationen des Systems |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/archive.svg) | archive | **Archiv** für alles, was mit dem Archivieren zu tun hat |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/archive2.svg) | archive2 | **Archiv Alternative** Alternative, die auch für Ordner o.ä. verwendet werden kann |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/archive3.svg) | archive3 | **Archiv Alternative** Alternative, die auch für Ordner o.ä. verwendet werden kann |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/assessment.svg) | assessment | **Prüfungen/Asssessments** allgemeines Icon für Prüfungen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/audio.svg) | audio | **Audio-Element** allgemeines Icon für Audio-Inhalt |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/audio3.svg) | audio3 | **Audio-Element** allgemeines Icon für Audio-Inhalt, Variante für Audio-Medienobjekt |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/billboard.svg) | billboard | **Schwarzes Brett** Icon für schwarze Bretter in Stud.IP |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-accordion.svg) | block-accordion | **Block-Icon für Akkordeon** Icon für den Courseware-Block Akkorden|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-canvas.svg) | block-canvas | **Block-Icon für Canvas** Icon für den Courseware-Block Canvas|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-comparison.svg) | block-comparison | **Block-Icon für Comparison** Icon für den Courseware-Block Bildvergleich|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-eyecatcher.svg) | block-eyecatcher | **Block-Icon für Eye-catcher** Icon für den Courseware-Block Blickfang|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-eyecatcher2.svg) | block-eyecatcher2 | **Block-Icon für Eye-catcher** Alternativ-Icon für den Courseware-Block Blickfang|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-gallery.svg) | block-gallery | **Block-Icon für Galerie** Icon für den Courseware-Block Bildergalerie|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-gallery2.svg) | block-gallery | **Block-Icon für Galerie** Alternativ-Icon für den Courseware-Block Bildergalerie|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-imagemap.svg) | block-imagemap | **Block-Icon für Imagemap** Icon für den Courseware-Block Imagemap|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-imagemap2.svg) | block-imagemap2 | **Block-Icon für Canvas** Alternativ-Icon für den Courseware-Block Imagemap |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-tabs.svg) | block-tabs | **Block-Icon für Tabs** Icon für den Courseware-Block Tabs |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-typewriter.svg) | block-tabs | **Block-Icon für Schreibmaschinen** Icon für den Courseware-Block Schreibmaschine |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/blubber.svg) | blubber | **Blubber** Icon für die Blubber-Funktion |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/brainstorm.svg) | brainstorm | **Brainstorm** Icon für das Brainstorm-Plugin |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/bus.svg) | bus | **Bus** Icon für Navigationsfunktionen, zB. in Campus-Apps |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/campusnavi.svg) | campusnavi | **Campus-Navi** Icon für Navigationsfunktionen allgemein, zb. in Campus-Apps |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/category.svg) | category | **Kategorie** allgemeines Icon für Kategorien |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/cellphone.svg) | cellphone | **Telefon/Handy** Telefonnummer, Smartphone usw. |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/chat.svg) | chat | **Chat** Chat/Forum/Messenger) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/check-circle.svg) | check-circle | **Akzeptieren/akzeptiert** für Popover |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/checkbox-checked.svg) | checkbox-checked | **markierte Checkbox** Checkbox in Form eines Icons, markiert |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/checkbox-unchecked.svg) | checkbox-unchecked | **unmarkierte Checkbox** Checkbox in Form eines Icons, unmarkiert |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/checkbox-indeterminate.svg) | checkbox-indeterminate | **uneindeutige Checkbox** Checkbox in Form eines Icons, uneindeutig (für Mehrfachselektion) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/radiobutton-checked.svg) | radiobutton-checked | **markierter Radiobutton** Radiobutton in Form eines Icons, markiert |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/radiobutton-unchecked.svg) | radiobutton-unchecked| **unmarkierter Radiobuttom** Radiobutton in Form eines Icons, unmarkiert |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/classbook.svg) | classbook | **Klassenbuch** Klassenbuch |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/clipboard.svg) | clipboard | **Zwischenablage** Kopieren in Zwischenablage |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/cloud.svg) | cloud | **Cloud-Service** generisches Icon für Cloudservices |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/code.svg) | code | **Programmcode** generisches Icon für Programmcode |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/code-qr.svg) | code-qr | **QR-Code** QR-Code |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/computer.svg) | computer | **Computer** allgemeines Computer-Icon |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/comment.svg) | comment | **Kommentar** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/comment2.svg) | comment2 | **Kommentar** Alternative für Kommentare|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/community.svg) | community | **Community** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/computer.svg) | computer | **Computer** allgemeines Icon für Computer (analog zu Telefon/Smartphone)|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/consultation.svg) | consultation | **Sprechstunden** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/content2.svg) | content | **Inhalte** allgemeines Icon für Inhalte|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/courseware.svg) | courseware | **Basis-Icon Courseware** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/crop.svg) | crop | **Beschneiden** Beschneiden von Bildern (zB. Avatar-Bilder) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/crown.svg) | crown | **Krone** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/date.svg) | date | **Termin** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/date-single.svg) | date-single | **Einzeltermin** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/date-cycle.svg) | date-cycle | **regelmäßiger Termin** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/date-block.svg) | date-block | **Blocktermin** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/decline.svg) | decline | **ablehnen** Dieses Symbol ist die Grundform für negative Rückmeldungen an den Nutzer. |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/decline-circle.svg) | decline-circle | **ablehnen** Ablehnen-Variante im Kreis|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/dialog-cards.svg) | dialog-cards | **Visitenkarten** Icon für Visitenkarten/Adressen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/doctoral-cap.svg) | doctoral-cap | **Prüfungen/Abschlüsse** allgemeines Icon für Prüfungen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/doit.svg) | doit | **Do.IT**Do.IT-Plugin und andere aufgabenbezogene Funktionen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/door-enter.svg) | door-enter | **Login/Betreten** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/door-leave.svg) | door-leave | **Logout/Verlassen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/download.svg) | download | **Download** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/dropbox.svg) | dropbox | **Cloud-Service Dropbox**Alternative |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/edit.svg) | edit | **Editieren** allgemeines Editieren-Icon |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/elmo.svg) | elmo | **Elmo** Icon für Elmo-Plugin |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/eportfolio.svg) | eportfolio | **ePortfolio-Icon** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/euro.svg) | euro | **Euro** Währungszeichen/Geld |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/evaluation.svg) | evaluation | **Evaluation** generelles Icon für Evaluationen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/exclaim.svg) | exclaim | **Hinweis** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/exclaim-circle.svg) | exclaim-circle | **Hinweis** für Popover |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/export.svg) | export | **Export** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/facebook.svg) | facebook | **Facebook** Facebook-Anbindung oder Verknüpfung * |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/favorite.svg) | favorite | **Favorit/Like** Favoriten-Icon * |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file.svg) | file | **Dokument** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/files.svg) | files | **Dokumente/Dateibereich** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-archive.svg) | file-archive | **Zip-Datei** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-audio.svg) | file-audio | **Audio-Datei** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-audio2.svg) | file-audio2 | **Audio-Datei** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-sound.svg) | file-sound | **Audio-Datei** Alternative, zB. für laute Audiodateien|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-pic.svg) | file-pic | **Bilddatei** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-pic2.svg) | file-pic2 | **Bilddatei** Alternative|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-pdf.svg) | file-pdf | **PDF-Datei** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-presentation.svg) | file-presentation | **Präsentation-Datei** generische Variante, ohne Power Point-Bezug
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-spreadsheet.svg) | file-spreadsheet | **Tabellenkalkulation'-Datei** generische Variante, ohne Excel-int-Bezug
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-office.svg) | file-office | **Office-Dokument** Word/PowerPoint/Excel |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-excel.svg) | file-excel | **Excel-Dokument** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-video.svg) | file-video | **Video-Datei** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-video2.svg) | file-video2 | **Video-Datei** Alternative |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-word.svg) | file-word | **Word-Dokument** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-ppt.svg) | file-ppt | **PPT-Dokument** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-text.svg) | file-text | **Textdatei** (zB. Word) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-generic.svg) | file-generic | **generischer Dateityp** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/filter.svg) | filter | **Suchfilter, Ansichtsfilter** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/filter2.svg) | filter | **Suchfilter, Ansichtsfilter** Alternative|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/fishbowl.svg) | fishbowl | **Goldfisch im Glas** ungenutzt|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-broken.svg) | folder-broken | **nicht erreichbarer Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-date-full.svg) | folder-date-full | **gefüllter Terminordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-date-empty.svg) | folder-date-empty | **leerer Terminordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-edit-empty.svg) | folder-edit-empty | **leerer editierbarer Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-edit-full.svg) | folder-edit-full | **voller editierbarer Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-empty.svg) | folder-empty | **leerer Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-full.svg) | folder-full | **gefüllter Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-group-empty.svg) | folder-group-empty | **leerer Gruppenordner**|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-group-full.svg) | folder-group-full | **gefüllter Gruppenordner**|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-home-empty.svg) | folder-home-empty | **leerer Home-Ordner**|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-home-full.svg) | folder-home-full | **gefüllter Home-Ordne**|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-inbox-full.svg) | folder-inbox-full | **gefüllter Inbox-Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-inbox-empty.svg) | folder-inbox-empty | **leerer Inbox- Ordner**|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-lock-full.svg) | folder-lock-full | **gefüllter geschützter Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-lock-empty.svg) | folder-lock-empty | **leerer geschützter Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-parent.svg) | folder-parent | **übergeordneter Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-plugin-market-empty.svg) | folder-plugin-market-empty.svg) | **Ordner Plugin-Marktplatz leer** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-plugin-market-full.svg) | folder-plugin-market-full.svg) | **Ordner Plugin-Marktplatz gefüllt** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-public-empty.svg) | folder-public-empty.svg) | **öffentlicher Ordner, leer** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-public-full.svg) | folder-public-full.svg) | **öffentlicher Ordner, gefüllt** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-topic-empty.svg) | folder-topic-empty | **leerer Themenordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-topic-full.svg) | folder-topic-full | **gefüllter Themenordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/forum.svg) | forum | **Forum** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/fullscreen-on.svg) | fullscreen-on | **Vollbild ein** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/fullscreen-off.svg) | fullscreen-off | **Vollbild aus** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/globe.svg) | globe | **Globus/Weltkarte** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/glossary.svg) | glossary | **Glossar** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/graph.svg) | graph | **Graph/Auswertung** generelles Icon für grafische Auswertungen (zB. Evalautionsauswertung) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/group.svg) | group | **Permalink** neu, ehemals Gruppieren |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/group2.svg) | group2 | **Gruppe/gruppieren** Gruppen (Menschen) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/group3.svg) | group3 | **Gruppe/Hierarchie** Gruppen/Hierarchie |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/group4.svg) | group4 | **Gruppe/gruppieren** Gruppieren nach Farbe |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/guestbook.svg) | guestbook | **Gästebuch** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/hamburger.svg) | hamburger | **Hamburger-Menu** für mobile Ansicht|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/home.svg) | home | **Startseite** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/info.svg) | info | **Information** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/info-circle.svg) | info-circle | **Information** für Popover |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/infopage.svg) | infopage | **Freie Infoseite** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/inbox.svg) | inbox | **Nachrichten Eingang** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/outbox.svg) | outbox | **Nachrichten Ausgang** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/install.svg) | install | **Plugin Installation** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/institute.svg) | institute | **Einrichtung** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/item.svg) | item | **Allgemeines Objekt für Kommentare** Kommentarobjekt |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/key.svg) | key | **Password** Password(-verwaltung) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/knife.svg) | knife | **Taschenmesser/Tool** alternative für Tool-Icon |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/learnmodule.svg) | learnmodule | **Lernmodul** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/lightbulb.svg) | lightbulb | **Glühbirne** etwa für Tipps, Ideen oder Brainstorming|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/link-extern.svg) | link-extern | **externer Link** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/link-intern.svg) | link-intern | **interner Link** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/link2.svg) | link2 | **externer Link** Alternative, rechts-orientierte Seiten|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/link3.svg) | link3 | **externer Link** Alternative, links-orientierte Seiten|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/literature-request.svg) | literature-request | **Literaturanfrage** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/literature.svg) | literature | **Literatur** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/lock-locked.svg) | lock-locked | **Schloss im geschlossenen Zustand** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/lock-unlocked.svg) | lock-unlocked | **Sperren/Schloss im geöffneten Zustand** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/log.svg) | log | **Protokoll/Log** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/mail.svg) | mail | **Nachricht** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/maximize.svg) | maximize | **Maximieren** für Widgetsystem|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/medal.svg) | medal | **Prüfungen/erreichte Leistungen** allgemeines Icon für Prüfungen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/mensa.svg) | mensa | **Mensa** Mensa, vegetarisch |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/mensa2.svg) | mensa2 | **Mensa** Mensa |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/metro.svg) | metro | **U-Bahn, Bahn** zB. für Campus-App |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/microphone.svg) | microphone | **Mikrofon** zB. für Medien |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/module.svg) | module | **Modul** in Abgrezung zu Lernmodul oder Plugin |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/money.svg) | money | **Mensa** Bezahlvorgänge, Kostenpflichtiges |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/network.svg) | network | **Netzwerk** ungenutzt |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/news.svg) | news | **Ankündigungen** Ankündigungen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/notification.svg) | notification | **Notifikation** Notifikation |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/notification2.svg) | notification2 | **Notifikation** Notifikation, Alternative |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/outer-space.svg) | outer-space | **Planet/Weltall** ungenutztes Icon, frei zur Nutzung |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/oer-campus.svg) | oer-campus | **OER-Campus** Basisicon für den OER-Campus|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/opencast.svg) | opencast | **Opencast** Icon für das Opencast-Plugin|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/perle.svg) | perle | **Perle** Icon für Perle Plugin |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/permalink.svg) | permalink | **Permalink** Icon zum Abrufen/Verlinken eines Permalinks |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/person.svg) | person | **Person/Profil** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/persons.svg) | persons | **Personen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/person-online.svg) | person-online | **Person online** Person ist online |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/picture.svg) | picture | **Bild** allgemeines Icon für Bilder, zB. in Courseware|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/phone.svg) | phone | **Telefon** klassisches Telefon, abgegrenzt von Handy|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/place.svg) | place | **Ort** Ort/Geoinformation/Place |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/plugin.svg) | plugin | **Plugin** Allgemeines Icon für Plugins |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/powerfolder.svg) | powerfolder | **Clound-Dienst Powerfolder** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/print.svg) | print | **Drucken** Druckfunktionen, Druckansicht |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/privacy.svg) | privacy | **Privatsphäreneinstellungen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/remove.svg) | remove | **Entfernen** Entfernen, auch im Sinne von Verringen (korrespondiert mit dem Entfernen-Zusatz) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/add.svg) | add | **Hinzufügen** Hinzufügen, auch im Sinne von Erhöhen (korrespondiert mit dem Hinzufügen-Zusatz) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/question.svg) | question | **Frage** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/question-circle.svg) | question-circle | **Frage** für Popover |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/ranking.svg) | ranking | **Ranking** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/radar.svg) | radar. | **Radar** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/refresh.svg) | refresh | **aktualisieren** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/resources.svg) | resources | **Ressource/Ressourcenverwaltung** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/resources-broken.svg) | resources-broken | **kaputte Ressource** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/resource-label.svg) | resource-label | **Ressourcen-Label** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/rescue.svg) | rescue | **Support/Hilfe Alternative** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/roles.svg) | roles | **Rollen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/roles2.svg) | roles2 | **Alternative für Rollen/Rechte** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/rotate-left.svg) | rotate-left | **Bildbearbeitung drehen links** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/rotate-right.svg) | rotate-right | **Bildbearbeitung drehen rechts** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/room.svg) | room | **Basisicon für Räume** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/room2.svg) | room2 | **Basisicon für Räume** Alternative |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/room-clear.svg) | room-clear | **Raum frei** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/room-occupied.svg) | room-occupied | **Raum belegt** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/room-request.svg) | room-request | **Raum anfrage** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/remove.svg) | remove | **Entfernen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/remove-circle.svg) | remove-circle | **Entfernen** für Popover |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/rotate-left.svg) | rotate-left | **Drehen gegen den Uhrzeigersinn** für Bildbearbeitung |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/rotate-right.svg) | rotate-right | **Drehen im Uhrzeigersinn** für Bildbearbeitung |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/rss.svg) | rss | **RSS-Feed** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/schedule.svg) | schedule | **Kalender/Ablaufplan** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/settings.svg) | settings | **Einstellungen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/settings2.svg) | settings2 | **Einstellungen** Alternative für Einstellungen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/share.svg) | share | **Teilen/Exportieren** allgemeines Icon für das Teilen von Objekten |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/search.svg) | search | **Suche** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/seminar.svg) | seminar | **Veranstaltung** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/seminar-archive.svg) | seminar-archive | **Veranstaltungsarchiv** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/smiley.svg) | smiley | **Smiley/Emoji** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/skype.svg) | skype | **Skype** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/span-empty.svg) | span-empty | **Füllstand/Progress: 0%** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/span-1quarter.svg) | span-1quarter | **Füllstand/Progress: 25%** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/span-2quarter.svg) | span-2quarter | **Füllstand/Progress: 50%** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/span-3quarter.svg) | span-3quarter | **Füllstand/Progress: 75%** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/span-1third.svg) | span-1third | **Füllstand/Progress: 33%** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/span-2third.svg) | span-2third | **Füllstand/Progress: 66%** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/span-full.svg) | span-full | **Füllstand/Progress: 100%** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/spiral.svg) | spiral | **Spirale** ungenutzt |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/sport.svg) | sport | **Sport** zB. für Campus-App |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/smiley.svg) | smiley | **Smiley** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/staple.svg) | staple | **Anhang** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/star.svg) | star | **Bewertungsstern** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/stat.svg) | stat | **Statistiken** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/studygroup.svg) | studygroup | **Studiengruppe** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/support.svg) | support | **Support** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/table-of-contents.svg) | table-of-contents | **Inhaltsverzeichnis** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/tag.svg) | tag | **Tag** Tags an Systemobjekten |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/tan.svg) | tan | **TAN** Vergabe, Nutzung von TANs, Prüfungen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/tan2.svg) | tan2 | **TAN** Vergabe, Nutzung von TANs, Prüfungen, Alternative |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/test.svg) | test | **Test** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/timetable.svg) | timetable | **Timetable** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/tools.svg) | tools | **Tools** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/topic.svg) | topic | **Thema** für Themen in Veranstaltungen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/trash.svg) | trash | **Mülleimer/löschen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/twitter.svg) | twitter | **Twitter** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/twitter2.svg) | twitter2 | **Alternative Twitter** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/twitter3.svg) | twitter3 | **Alternative Twitter** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/ufo.svg) | ufo | **Ufo** gibt es sie wirklich?|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/unit-test.svg) | unit-test | **Unit-Tests** Unit-Tests |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/upload.svg) | upload | **Upload** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/vcard.svg) | vcard | **vCard/Visitenkarte** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/video.svg) | video | **Video** Video |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/video2.svg) | video2 | **Video** Video/Film |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/view-list.svg) | view-list | **Umschalter Liste/Kacheln Liste** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/view-wall.svg) | view-wall | **Umschalter Liste/Kacheln Kacheln/Wall** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/visibility-checked.svg) | visibility-checked | **Sichtbarkeit an** Umschalter Sichtbarkeit: an |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/visibility-visible.svg) | visibility-visible | **sichtbar/Sichtbarkeit an** Objekt ist sichtbar oder Umschalter Sichtbarkeit: aus |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/visibility-invisible.svg) | visibility-invisible | **unsichtbar** Objekt ist unsichtbar |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/vote.svg) | vote | **Umfrage** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/vote-stopped.svg) | vote-stopped | **angehaltene Umfrage** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/wiki.svg) | wiki | **Wiki** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/wizard.svg) | wizard | **Assistent** Icon für Assistenten |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/youtube.svg) | youtube | **Youtube** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/zoom-in.svg) | zoom-in | **Zoom in** Zoomen für Bildupload |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/zoom-in2.svg) | zoom-in2 | **Zoom in** Zoomen für Bildupload, Alternative |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/zoom-out.svg) | zoom-out | **Zoom out** Zoomen für Bildupload|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/zoom-out2.svg) | zoom-out2 | **Zoom out**Zoomen für Bildupload, Alternative |
+
+
+##### Play Pause und Stop
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/play.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/stop.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/pause.svg)
+
+
+##### Listen und Pfeile
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_1down.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_1left.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_1right.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_1up.svg) Pfeile, Blätterfunktion (zB. Seite weiter, vor/zurück)\\
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_2down.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_2left.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_2right.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_2up.svg) Pfeile zum Verschieben (zB. Objekt eintragen, Vertauschen von Objekten use.)\\
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_eol-down.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_eol-left.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_eol-right.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_eol-up.svg) Pfeile zum Springen an das Ende einer Liste (Springen an das Ende einer Tabelle, finer Liste von Objekten usw.)
+
+# Schrift
+
+## Vorbemerkung
+
+In Stud.IP wird eine einheitliche Schriftart verwendet. Im Gegensatz zu früheren Versionen, in dem Betriebssystem und Browser die Schriftart bestimmt haben (in der Regel Arial oder Helvetica, serifenlose Schriftarten), liegt die Festlegung nun in den Stylesheets von Stud.IP. Das bietet eine Reihe von Vorteilen (festgelegte Formatierung Größen, tendenziell mehr wie in Print-Designs) aber auch Nachteile (unterschiedliche Darstellung, Lesbarkeit, Probleme mit Webfonts in bestimmten Browsern).
+
+## Schriftart Lato
+
+In Stud.IP kommt durchweg die Schriftart Lato zum Einsatz. Lato ist ein frei verfügbarer Google Font:
+
+https://www.google.com/fonts/specimen/Lato
+
+# CSS (Übersicht)
+
+## Interaktionselemente
+ Text muss noch aktualisiert werden
+
+* Was soll anklickbar sein?
+ * Navigationselemente
+ * Buttons mit Text
+ * Textlinks: Textlinks sollen nicht im Fließtext von Systembestandteilen (zB. in Onfoboxen) erscheinen, auch wenn dies in der Vergangenheit häufig so gemacht wurde. Textlinks erscheinen nur im Fließtext eines Feldess (Nutzereingabe oder Systemfeld) und werden durch die Formatierungsfunktionen in diesem Fall mit dem Link-Icon versehen. Nur in diesem Fall kann der Nutzer diesen Link leicht vom Fließtext unterscheiden.
+ * Icons: Grundsätzlich sollen Icons nur in der blauen Variante klickbar sein (die Farbe entspricht der normalen Linkfarbe), Ausnahmen gibt es derzeit auf der Seite "Meine Veranstaltungen" und bei einigen Objekticons. Die Ausnahmen sollen Ausnahmen bleiben, dh. neue klickbare Icons sind grundsätzlich in Blau zu halten. Für Aktionen wie Anlegen, löschen oder Verschieben gibt es Zusätze (in der Regel rot), die auf die besondere Funktion dieses Icons hinweisen. (siehe unter [Icons](Visual-Style-Guide#Icons))
+
+* Wann benutzt man zur Interaktion einen Textlink, wann einen Tab, wann einen Button mit Text und wann ein Icon?
+ * Tab: Umschalten zwischen verschiedenen Ansichten/Aspekten eines Objekts
+ * Textlink: Navigation zu einer anderen Seite, keine "Aktion" i.e.S.
+ * Icon: Auslösen einer Aktion
+ * Button: Auslösen einer Aktion
+ * Wann benutzt man ein Icon und wann einen Button?
+
+* Wie sollen Buttons benannt werden?
+ * Beispiel: "übernehmen" vs. "abschicken" vs. "OK" vs. "speichern" vs. ...
+
+* Welche Icons stehen für welche Aktionen?
+ * Beispiel: gelber Doppelpfeil (sortieren? Objekte verschieben? Objekte kopieren? Weiterblättern?)
+ * Müsste für alle Icons durchgegangen werden.
+ * IconListe
+
+* Grundlegendes
+ * Wozu dienen Icons?
+ * Wann sollen interaktive Icons, wann Buttons, wann Text-Links verwendet werden?
+ * Buttons
+ * zum Auslösen von Aktionen
+ * im Inhaltsbereich
+ * Icons
+ * zum Auslösen von Aktionen
+ * außerhalb des Inhaltsbereichs
+ * oder dort, wo für Buttons nicht genug Platz ist (z. B. in Tabellen)
+ * Textlinks
+ * zum Wechsel auf andere Seiten innerhalb von Stud.IP oder aus Stud.IP heraus
+ * nicht zum Auslösen von Aktionen
+ * überall (im Inhaltsbereich, in Infoboxen usw.)
+
+### Buttons
+
+Stud.IP verwendet klar erkennbare Buttons zum Bestätigen und Abschließend von Aktionen sowie gelegentlich zur Initiierung von Hauptaktionen im System. Haupteinsatzzweck von Buttons ist das Abschließen von Aktionen, insbesondere am Ende/Fuß eines Dialoges oder beim Löschen von Objekten.
+Als Faustformel, wann ein ein Button und wann ein Icon verwendet werden kann gilt:
+
+* Initiierung: In der Rel werden die meisten Aktionen in Stud.IP durch Icons (teilweise mit daneben gesetztem Text) initiiert. Typische Beispiele sind die Sidebar, Icons in Tabellenköpfen oder -Zeilen und sämtliche Icons in der Hauptnavigation. Dies ist einerseits durch Größeneinschränkungen begründet (Icons benötigen signifikant weniger Platz) oder das Vorhandensein einer Vielzahl an Aktionen (drei oder mehr Icons neben oder gar untereinander sind für die Usabilty besser darstellbar, als die gleiche Anzahl an Buttons). Es gibt jedoch auch Kontexte, in den Buttons zur Initiierung verwendet werden können. Dies ist insbesondere dann sinnvoll, wenn die Umgebung der Seite ausreichend Platz bietet und keine Standardelemente (Tabellen oder Content-Boxen nutzen in der Regel ausschließlich Icons) verwendet werden. Gleichzeit können Buttons verwendet werden, wenn eine Aktion besonderes Gewicht bekommt (zB. das Löschen eines Objektes).
+* Bestätigung und Abschließen: Ein guter Einsatzzweck eines Buttons ist Stets das Bestätigen und Abschließen einer Aktion, etwa am Ende eines Dialoges nach dem Eingeben einer Vielzahl von Daten. Auch das Löschen, am Ende eines Dialoges oder eines Formular sowie das Abbrechen von Dialogen sind typische Einsatzzwecke eines Buttons.
+
+Zu Beachten ist, dass ein Button stets eine Aktion als ausgeschriebenes Wort, jedoch in der Regel ohne Icon zeigt. Icons hingegen sind oft lediglich in ihrer grafischen Form zu sehen, werden jedoch häufiger (Sidebar, Aktionsmenu) auch mit Text ergänzt. Dennoch ist ein Button stets dass "gewichtigere" Interaktionselement, da er größer ist, einen Hover-Effekt bietet und auch (im Verhältnis zu Icons) durch seien große Fläche leichter zu bedienen ist. Das gilt insbesondere für Touch-Geräte.
+
+#### Beschriftung und Labelling
+
+Buttons werden in der Regel mit der Aktion des Namens als Substantive beschriftet (das "Abschließen", das "Abbrechen" und das "Speichern"). Substantiv-Verb-Konstruktionen sind zu vermeiden ("Speichern" wäre dem Button "Datei speichern" vorzuziehen). Ganze Sätze ("Diese Datei speichern.") sind nicht zulässig. Ideal ist also ein Button, der lediglich ein Wort enthält.
+Dabei soll die Beschriftung die Funktion des Buttons möglichst klar beschreiben. Es soll erkennbar sein, was passieren wird, wenn man den auf den Button drückt.
+Bei der Wortwahl sind jedoch spezifische substantivierte Verben zu verwenden. "OK" ist kein guter Button, da nicht klar erkennbar ist, was passiert. "Speichern" hingegen beschreibt einen Button korrekt.
+Im Standarddesign wird die Beschriftung automatisch zentriert ausgegeben.
+
+
+#### Erscheinungsbild
+Buttons erscheinen in Stud.IP als weiße Buttons mit starken, dunkelblauen Rand. Buttons werden als knickbares Objekt grundsätzlich Blau eingefärbt. Rote oder Grüne bzw. anders gefärbte Buttons sind nicht erlaubt. Eine gefährliche Aktion (zB. "Löschen") muss einerseits durch geeignete Platzierung des Buttons und durch weitere Absicherungen (Warndialog) geschützt werden, nicht jedoch durch Warnfarben des Buttons.
+In einigen Fällen sind noch Icons in Buttons zu sehen (Haken, X oder ähnliches). Diese Ergänzung eines Buttons durch Icons ist nicht mehr zulässig.
+
+#### Platzierung und Ausrichtung der Buttons
+
+Buttons dürfen nicht im Textfluss platziert werden und müssen immer freistehendend platziert werden. Idealerweise gibt es keine weiteren Objekte links und rechts eines oder mehrerer Buttons im gestaltbaren Bereich (zB. Dialog, Tabellenzeile, Content-Box).
+In Formularen sind Buttons linksbündig zu setzen, in Dialogen mittig. Allerdings gibt es Ausnahmen, wenn die Funktion des Buttons eines bestimmte Position impliziert. So sollten "Zurück" und "Weiter"-Buttons innerhalb eines Dialoges entsprechend linksbündig und rechtsbündig gesetzt werden.
+
+#### Reihenfolge mehrerer Buttons nebeneinander:
+Für Buttons soll eine bestimmte Reihenfolge eingehalten werden:
+1. Positiver, bestätigender Button ("Speichern", "Übernehmen", "Ja")
+2. Negativer Button ("Nein")
+3. Harmloser/abbrechender Button, der den Zustand nicht verändert ("Abbrechen", "Schließen")
+
+### Verhalten
+Stud.IP kennt neben aktiven Buttons auch inaktive Buttons. Diese sind ausgegraut und können nicht geklickt werden, weisen aber darauf hin, dass unter anderen Umständen dieser Button klickbar wäre (hier kann ein Info-"i"-Icon mit Tooltip daneben erklären, warum der Button nicht klickbar ist).
+Sollte ein Default-Button definiert sein, der bei Tastendruck (z.B. Enter) aktiviert wird, muss dies stets ein harmloser Button ("Nein", "Abbrechen" oder "Schließen") sein. Für den Fall, dass bereits Daten in einem zugehörigen Formular erfasst wurden, gibt es (zumindest in Dialogen) eine Eigenschaft, die das Schließen des Dialoges nach Eingabe ohne eine weitere Bestätigung verhindert. Generell ist darauf zu achten, dass eine Default-Option keinen Datenverlust nach sich ziehen kann.
+
+----
+
+**Allgemein** \\
+Nutze Bestätigungs-Buttons nach den folgenden Design-Patterns:
+
+
+
+| **Pattern** | **Commit buttons** |
+| ---- | ---- |
+| Frage-Dialog (mit Buttons) | Eine der folgenden spezifischen Bezeichnergruppen: Ja/Nein, Ja/Nein/Abbrechen, [Do it]/Abbrechen, [Do it]/[Don't do it], [Do it]/[Don't do it]/Abbrechen|
+| Auswahl-Dialoge | **Modaler Dialog:** OK/Abbrechen oder (Do it)/Abbrechen
+| |**Nicht-modaler Dialog**: Schließen-Button in der Dialogbox und der title bar|
+
+## Aktionsmenüs
+
+![image](../assets/3dd1c5b5758d79e52a77165d721e1665/image.png)
+
+Ein Aktionsmenü verkapselt eine Liste von kontextbezogenen Aktionen und kann an folgenden Stellen verwendet werden:
+* Bei Aktionen für ein Element in einer Liste oder Tabelle.
+ * Beispiele: Teilnehmer, Veranstaltungen, Fragebögen
+* Bei Aktionen für einen Bereich, der einen Inhalt umschließt und keine eigenen Aktions-Buttons hat.
+ * Beispiele: Tabellen, Gruppen von Personen, Widgets auf der Startseite
+
+In diesem Fall steht das Aktionsmenü ganz rechts, wo sonst die Aktions-Icons einzeln aufgeführt sind. Generell sollte ein Aktionsmenü anstelle einer Auflistung von Icons eingesetzt werden, wenn mehr als drei Aktionen direkt nebeneinander stehen (dabei zählen auch inaktive bzw. ausgeblendete Aktionen mit) oder die Icons nicht selbsterklärend sind und durch Text erklärt werden sollen.
+
+Allgemeine Richtlinien bei der Verwendung eines Aktionsmenüs:
+* Die primäre Aktion eines Elements (z.B. Bearbeiten, Anzeigen, Aufklappen) ist nicht Teil des Menüs.
+* Die primäre Aktion ist immer durch Klick auf das Element selbst (ggf. auch dessen Icon) zugänglich.
+* Wenn das Element aufklappbar ist, ist aufklappen bzw. zuklappen immer die primäre Aktion.
+* Inaktive Aktionen sollten nicht komplett versteckt, sondern nur inaktiv (grau, nicht anklickbar) angezeigt werden.
+* Ist die primäre Aktion nicht verfügbar, so ist das Element nicht anklickbar, es gibt dann nur die Aktionen im Menü.
+* Es dürfen bis zu zwei Icons außerhalb des Menüs (d.h. davor) platziert werden, falls diese oft verwendet werden.
+* Wenn eine Spalte einer Tabelle das Aktionsmenü verwendet, sollten konsistent alle Zeilen der Tabelle dieses nutzen.
+* Icons im Aktionsmenü sollten nicht verwendet werden, um den Status eines Objekts anzuzeigen.
+
+Vorgeschlagene Reihenfolge der Aktionen im Menü, sofern die einzelnen Aktionen vorhanden sind:
+* Anzeigen (oder Vorschau)
+* Herunterladen
+* Aktualisieren
+* Bearbeiten
+* Hinzufügen (oder Zuordnen)
+*
+* Verschieben
+* Kopieren
+* Exportieren
+*
+* Löschen (oder Austragen)
+
+Offene Fragen:
+* Sollte bei hinreichend breiten Displays oder durch eine Nutzereinstellung das Menü in einzelne Aktions-Icons aufgelöst werden?
+* Soll auf Mobilgeräten bzw. "kleinen" Anzeigebreiten das Aktionsmenü auch bei drei oder weniger Aktionen auftauchen?
+ * Schließt das dann auch die ggf. explizit vor dem Menü plazierten (bis zu zwei) Icons ein?
+* Sollen die Aktions-Icons generell einen Hover-Effekt bekommen? Beispiel: Cliqr
+
+# Dialoge (Modale Dialoge)
+
+Dialoge werden verwendet um Eingaben oder Bestätigungen vom Benutzer einzuholen.
+Seit der Einführung der Sidebar gelten Dialoge auch grundsätzlich als Repräsentation von Aktionen.
+Aktionen verbleiben dabei auf der jeweiligen Seite und vermeiden einen Kontextwechsel
+
+Trotzdem sollten Dialoge nicht inflationär eingesetzt werden. Wenn möglich sollte eine Interaktion direkt auf der Seite stattfinden,
+auf der auch die zu bearbeitenden Informationen oder Elemente dargestellt werden.
+
+
+## Allgemeines
+Dialoge sollten einfach gehalten und nicht zu komplex sein, d. h. nur eine zugehörige Aktion sollte pro Dialog ausgeführt werden.
+
+Dialoge sollten selbst erklärend sein und möglichst wenig (Erklärungs-) Texte enthalten.
+
+Dialoge sollten nicht gestapelt werden, d. h. ein Dialog sollte sich nicht in einem Dialog öffnen (außer z . B. Datepicker, wo kein Button gedrückt werden muss). Für komplexere Dialoge sollten Wizards verwendet werden, in dene mehrere Dialoge hintereinander geschaltet sind oder Bereich innerhalb der Dialoge auf- und zugeklappt werden.
+
+## Modale und nicht-modale Dialogfenster
+Bei modalen Dialogfenstern kann der Benutzer nicht in anderen Fenstern der Anwendung weiterarbeiten, sondern nur im Dialogfenster. Im Unterschied dazu erlauben nicht-modale Dialogfenster dem Benutzer auch die Interaktion mit dem Hintergrundfenster. Nicht-modale Dialogfenster werden in Stud.IP beispielsweise beim Stundenplan zur Eingabe von Terminen verwendet. Die Mehrzahl der Dialoge in Stud.IP ist jedoch modal.
+
+Beim Aufruf eines modalen Dialogfensters wird das Hintergrundfenster durch geeignete optische Manipulation ("Ausgrauen" bzw. abdunkeln mit einem Overlay in Dunkelblau) als inaktiv gekennzeichnet.
+
+Dialoge sind abzugrenzen von Notifications, die nie modal sind und nicht als eigenes Fenster erscheinen.
+
+## Popup-Fenster
+
+Popup-Fenster, d. h. sich separat öffnende Fenster, dürfen nicht verwendet werden.
+
+## Eigenschaften
+Dialogfenster sind meist [formularartig](Visual-Style-Guide#Formulare) aufgebaut.
+
+Dialoge sollten lesbar sein, ohne dass ein Scrollen im Dialog erforderlich wird. Eine Ausnahme bildet das vertikale Scrollen: Wenn es sich nicht vermeiden lässt (etwa weil Inhalte, lange Listen oder Aufklappelemente nicht das Dialogfenster passen) darf innerhalb des Dialogfensters gescrollt werden.
+
+Die Seitengröße innerhalb eines Dialogfensters sollte sich während seiner Bearbeitung nicht ändern. Ausgenommen von dieser Einschränkung ist beispielsweise das dynamische Nachladen von Elementen einer Drop-Down-Liste oder das dynamische Einblenden von Ausfüllhinweisen bei Pflichtfeldern.
+
+## Verhalten
+Wenn man neben ein Dialogfester klickt, sollte sich dieses nicht schließen.
+
+Wenn der Benutzer den Button Escape drückt, schließt sich das Dialogfenster, es sei denn, es wurden bereits Eingaben gemacht. Hier muss der Auto-Formsaver aktiviert werden, so dass der Nutzer auf eventuelle verlorengehende Inhalte hingewiesen wird.
+
+
+### Schematischer Aufbau eines Dialogfensters
+![image](../assets/ce19cb16e52e35fa80ad2fd66ee7fbac/image.png)
+
+#### Layout/Design
+
+Grundsätzlich sind Dialoge gestalterisch so aufgebaut, dass sie von einer dunkelblauen Kopfzeile (Stud.IP-Brand-Color, siehe [DesignFarbe](DesignFarbe)) eingeleitet werden und einen weißen Hintergrund haben. Sie haben einen dünnen weißen Rand, einen leichten Schatten und dunkeln die dahinterliegende Seite ab. Buttons haben einen separaten Footer, analog zu Tabellen oder Formularen.
+
+Beispiel für das Design:
+
+![Bildschirmfoto_2021-11-15_um_15.35.11](../assets/c37f69398215d78b12784d3428c89a9c/Bildschirmfoto_2021-11-15_um_15.35.11.png)
+
+#### Text in der Titelleiste
+Der Titeltext sollte aussagekräftig und spezifisch sein, damit Benutzer genau wissen, was sie tun sollen. Eine Dopplung zum Content muss vermeiden werden.
+
+#### Buttons
+Jeder Dialog ein X-Icon rechts in der Titelleiste, um den Dialog schließen zu können. Zusätzlich gibt es einen separaten "Abbrechen"/"Schließen"-Button, da viele Benutzer das x-Icon in der Titelleiste übersehen.
+
+Auf dem Übernehmen-Button (accept-Button) wird ein Häkchen-Icon angezeigt.
+
+Der Text auf dem Übernehmen-Button sollte ein spezifisches Verb sein wie beispielsweise "Löschen" oder "Anlegen" und nicht nur "OK".
+
+### Sicherheitsabfragen
+Sicherheitsabfragen werden verwendet insbesondere beim Löschen wichtiger Elemente oder bei anderen kritischen und unwiderruflichen Aktionen.
+
+Sicherheitsabfrage sind eine vereinfachte Form des modalen Dialogs.
+
+Sie enthalten einen Hinweis- oder Fragetext und zwei Buttons zum Bestätigen und Verwerfen.
+
+# Formulare
+
+Formulare sollen in Stud.IP gemäß LifTers014 einheitlich gestaltet werden. Ein Standard Formular wird wie folgt definiert:
+
+```php
+<form class="default" ...>
+ ...
+</form>
+```
+
+
+## Allgemein
+Lange Erklärungstexte am Anfang des Formulars sollten vermieden werden. Erklärungen können über Tooltips an den Elementen (siehe unten) oder ggf. Texte in der Hilfelasche realisiert werden.
+
+## Gruppierung der Formularfelder
+Formularfelder (oder auch Input-Elemente) sollen gruppiert werden, wenn sie inhaltlich oder funktional zusammenhängen, damit dieser Zusammenhang deutlich wird. Jede Gruppe sollte eine passende Überschrift haben.
+
+```php
+<form class="default" ...>
+ <fieldset>
+ <legend>Gruppenüberschrift 1</legend>
+ ...
+ </fieldset>
+ <fieldset>
+ <legend>Gruppenüberschrift 2</legend>
+ ...
+ </fieldset>
+</form>
+```
+
+Auch Formulare mit lediglich einer Gruppierung sind zulässig. In Dialogen wird eine einzelne Gruppierung jedoch entfernt!
+
+#### Ein-/Ausblenden von Gruppen
+
+Einzelne Gruppen können aus- bzw. eingeblendet werden, indem entweder dem `fieldset` (für eine spezielle Gruppe) oder dem gesamten Formular (für alle Gruppen innerhalb) die Klasse `collapsable` gegeben wird. Dadurch wird durch einen Klick auf die `legend` des `fieldset`s die Gruppe versteckt bzw. wieder angezeigt. Soll die Gruppe bei der initialen Darstellung ausgeblendet sein, muss das `fieldset` zusätzlich mit der Klasse `collapsed` ausgezeichnet werden.
+
+```php
+<form class="default" ...>
+ <fieldset>
+ <legend>Gruppenüberschrift 1</legend>
+ ...
+ </fieldset>
+ <fieldset class="collapsable collapsed">
+ <legend>Gruppenüberschrift 2</legend>
+ ...
+ </fieldset>
+</form>
+```
+
+### Labels
+Generell sollte analog zu LifTers010 das HTML-Markup `<label>` verwendet werden. Beispiel:
+
+```html
+<form class="default" ...>
+ <fieldset>
+ <legend>Beschriftung</legend>
+ ...
+ <label>Eingabe A
+ <input name="eingabe_a" type="text" placeholder="Texteingabe A" required>
+ </label>
+ ...
+ </fieldset>
+ </form>
+```
+
+* Das erste Wort des Labels sollte mit einem großen Anfangsbuchstaben geschrieben werden.
+* Das Label sollte nicht mit einem Doppelpunkt abgeschlossen werden.
+
+Wording:
+* Es sollen aussagekräftige Labels gewählt werden.
+* Fachbegriffe sollen vermieden werden.
+* Keine ganzen Sätze.
+
+### Nicht änderbare / deaktivierte Eingabefelder
+
+Falls in einem Formular im aktuellen Kontext ein Feld nicht geändert werden darf, so muss das Attribut `disabled` an das Eingabefeld gehängt werden. Es ist nicht zulässig, einfach nur den Text OHNE Formularelement auszugeben!
+
+Beispiel aus lib/classes/StudipSemTreeViewAdmin.class.php
+```php
+<form class="default" ...>
+ <fieldset>
+ <legend><?= _("Bereich editieren") ?></legend>
+
+ <label>
+ <?= _("Name des Elements") ?>
+ <input type="text" name="edit_name"
+ <?= $this->tree->tree_data[$this->edit_item_id]['studip_object_id'])? 'disabled' : * ?>
+ value="<?= htmlReady($this->tree->tree_data[$this->edit_item_id]['name']) ?>">
+ </label>
+ ...
+ </fieldset>
+</form>
+```
+
+### Ausrichtung der Formularfelder
+
+Schmale Formularfelder dürfen in mehreren Spalten angeordnet werden. Bei schmaleren Anzeigen brechen diese Felder bei korrekter Anwendung automatisch um.
+
+Untereinander angeordnete Formularfelder sollten linksbündig angeordnet sein. Wenn mehrere Formularfelder eine logische Folge bilden oder aus anderen Gründen direkt zusammengehören, sollten Sie in einer Horizntalen Gruppierung (hgroup) angeordnet werden.
+
+#### Reguläre nebeneinader angeordnete Elemente
+
+Um Elemente bei passend großem Bildschirm nebeneinander anzuzeigen, werden diese in Spalten angeordnet. Es gibt insgesamt 6 Spalten und man Elementen eine Breite von 1 - 6 Spalten zuordnen. Dafür gibt es die Klassen col-1 bis col-5 - keine Angabe bedeutet dabei ganze Breite (enstpräche einem col-6).
+
+Diese Elemente werden dann bei schmaleren Anzeigen automatisch untereinander angezeigt.
+
+```php
+<form class="default" ...>
+ <label class="col-3">
+ Vorname
+ <input type="text" name="first-name">
+ </label>
+
+ <label class="col-3">
+ Nachname
+ <input type="text" name="last-name">
+ </label>
+</form>
+```
+
+
+#### Horizontale gruppiert angeordnete Elemente
+
+Um Elemente in einer Zeile horizontal zu gruppieren, benötigt es ein Wrapper-Element mit der Klasse `.hgroup`. Dieses Element nimmt die gleichen Größen wie die Elemente an und verteilt den Platz innerhalb von sich selbst erstmal gleich, aber die einzelnen Elemente können auch wiederum durch die bekannten Größenangaben beeinflusst werden.
+
+Die hgroup ist lediglich zulässig für kombinierte Eingabefelder, wie Telefonnummern, Datumsangaben etc. sowie RadioButtons mit sehr kurzen Labels (z.B. Geschlecht: m/w/kA, Schalter: ja/nein/kA, etc.). Es dürfen keine zu großen Felder und/oder zu lange Label-Texte bei der horizontalen Gruppierung verwendet werden!
+
+```php
+<form class="default" ...>
+
+ <!-- ... -->
+
+ <div>
+ <?= _('Geschlecht') ?>
+ </div>
+
+ <section class="hgroup">
+ <label>
+ <input type="radio" <? if (!$geschlecht) echo 'checked' ?> name="geschlecht" value="0">
+ <?= _("unbekannt") ?>
+ </label>
+
+ <label>
+ <input type="radio" <? if ($geschlecht == 1) echo "checked" ?> name="geschlecht" value="1">
+ <?= _("männlich") ?>
+ </label>
+
+ <label>
+ <input type="radio" name="geschlecht" <? if ($geschlecht == 2) echo "checked" ?> value="2">
+ <?= _("weiblich") ?>
+ </label>
+ </section>
+ <!-- ... -->
+</form>
+```
+
+Es gibt noch eine zweite Variante, die eingesetzt werden darf, wenn es sich bei dem Titel tatsächlich um das Label eines nachfolgenden Form-Elementes handelt. Beispiel aus der Nutzerverwaltung:
+
+```php
+<label for="inaktiv">
+ <?= _('inaktiv') ?>
+</label>
+
+<section class="hgroup">
+ <select name="inaktiv" class="size-s" id="inaktiv">
+ <? foreach(array('<=' => '>=', '=' => '=', '>' => '<', 'nie' =>_('nie')) as $i => $one): ?>
+ <option value="<?= htmlready($i) ?>" <?= ($request['inaktiv'][0] === $i) ? 'selected' : * ?>>
+ <?= htmlReady($one) ?>
+ </option>
+ <? endforeach; ?>
+ </select>
+
+ <label>
+ <input name="inaktiv_tage" type="number" id="inactive" value="0">
+ <?= _('Tage') ?>
+ </label>
+</section>
+```
+
+#### Kombinierte Variante mit col- und hgroup-Angaben
+
+Es ist ebenfalls möglich und zulässig, horizontal gruppierte Element in Spalten einzuteilen:
+
+```php
+<label class="col-3">
+ Telefonnummer
+ <section class="hgroup">
+ + <input type="text" size="3">
+ <input type="text" maxlength="5" class="no-hint" size="5"> /
+ <input type="text" maxlength="10" size="10">
+ </section>
+</label>
+
+<label class="col-3">
+ Fax
+ <section class="hgroup">
+ + <input type="text" size="3">
+ <input type="text" maxlength="5" class="no-hint" size="5"> /
+ <input type="text" maxlength="10" size="10">
+ </section>
+</label>
+```
+
+### Ausrichtung der Labels
+Die Labels sollen linksbündig und oberhalb der Eingabefelder angebracht sein. Dies erleichtert die Lesbarkeit der Beschriftungen und verdeutlicht den Zusammenhang zwischen den Feldbeschriftungen und den Eingabefeldern.
+
+Attach::formlabel2015.png
+
+Wenn der Platz in der Vertikalen beschränkt ist, sollen die Beschriftungen linksbündig und links neben den Formularfeldern angebracht sein. Dies erhält die Lesbarkeit und spart Platz in der Vertikalen. In diesem Fall sollten die Labels so gewählt werden, dass sie sich in ihrer Länge möglichst wenig unterscheiden, damit die Lücken zwischen den Labels und den Eingabefeldern nicht zu groß werden.
+
+Innerhalb eines Kontextes sollten die Beschriftungen einheitlich angeordnet werden.
+
+### Placeholder/Platzhalter
+Das placeholder-Attribut dient zum Befüllen von Eingabefeldern mit kurzen Hinweisen. Dieser Inhalt verschwindet, sobald ein Nutzer in das Eingabefeld klickt.
+* Placeholder sollten nicht als Alternative zum Label verwendet werden.
+* Placeholder sollten sparsam verwendet werden.
+
+Beispiel für ein korrekt verwendetes placeholder-Attribut:
+TODO: Screenshot
+
+
+Beispiel für ein **falsches** placeholder-Attribut:
+Attach::wronglabel.png
+
+
+## Art der Formularfelder
+Die Art der Eingabefelder soll so gewählt werden, dass man an ihr erkennen kann, welche Eingaben möglich sind. Ein Textfeld dient zur freien Eingabe von Zeichen ohne Beschränkungen (außer in der Zeichenanzahl). [Checkboxen](Checkboxen), [Radio Buttons](Visual-Style-Guide#RadioButtons) oder [Drop-Down Listen](DropDown) werden verwendet, um die Anzahl der Optionen einzuschränken oder für Einträge, wo sich Nutzer leicht vertippen.
+
+
+## Größe der Formularfelder
+Eingabefelder sollen groß genug sein, um typische Eingaben entgegen zunehmen, ohne dass man "über den rechten Rand hinausschreibt". Die Größe der Formularfelder soll so gewählt werden, dass sie deutlich machen, welche Eingaben dort möglich sind. Beispiel: Das Eingabefeld für die Veranstaltungsnummer sollte kürzer sein als das für den Veranstaltungstitel.
+
+Das Stud.IP-Stylesheet schlägt standardmäßig drei Größen vor (CSS-Klassen "size-s","size-m" und "size-l"):
+
+* size-s: 10em (gedacht für kurze Eingaben wie z.B. Zahlen)
+* size-m: 48em
+* size-l: 100%
+
+```php
+<form class="default" ...>
+...
+ <label>
+ Kurze Eingabe
+ <input type="text" class="size-s">
+ </label>
+
+ <label>
+ Mittlere Eingabe
+ <input type="text" class="size-m">
+ </label>
+
+ <label>
+ Längere Eingabe
+ <input type="text" class="size-l">
+ </label>
+...
+</form>
+```
+
+Attach::formsizes2015.png
+
+Die Voreinstellung ist "size-m". Ausnahme: Für die Input-Typen "number" und "date" ist die Voreinstellung "size-s".
+```php
+<form class="default narrow" ...>
+ ...
+</form>
+```
+
+### Schmale Formulare
+
+Manchmal ist es notwendig, ein Formular standardmäßig besonders platzsparend anzubieten (siehe z.B. Admin > Standort > Veranstaltungshierarchie).
+Dafür kann dem Formular die Klasse "narrow" hinzugefügt werden. Dies sorgt dafür, dass die einzelnen Formularelemente etwas enger zusammenrücken, um ein frühzeitiges umbrechen zu vermeiden.
+
+Attach::narrow_form.png
+
+## Kennzeichnung von Pflichtfeldern
+
+```php
+<form class="default" ...>
+ <fieldset>
+ <legend>Beschriftung</legend>
+ ...
+ <label>
+ <span class="required">Eingabe A</span>
+ <?= tooltipIcon(_('Bitte geben Sie hier nur eine Zahl ein')) ?>
+ <input type="number">
+ </label>
+ ...
+ </fieldset>
+ </form>
+```
+
+
+Pflichtfelder müssen mit einem hochgestellten roten Stern rechts neben der Feldbeschriftung gekennzeichnet werden. Die kann in einem Label mittels `<span class="required">` im Quelltext umgesetzt werden.
+
+### Hinweistexte zu den Formularfeldern [#Hinweistexte](#Hinweistexte)
+
+Da die Beschriftung eines Formularfelds möglichst kurz sein sollte, ist es möglich, dass weitere Informationen oder erläuternde Hinweise zum entsprechenden Feld nötig sind. Ein erforderlicher Hinweis- oder Beschreibungstext zu einem Formularfeld wird mittels Tooltip realisiert. Der Tooltip wird über die vorhandene Logik `<?= tooltipIcon(_('...'))?>` rechts neben dem Label und ggf. hinter der Kennzeichnung eines Pflichtfeld positioniert.
+
+Attach:formtooltip2015.png
+
+
+## Formatvorgaben und Eingabevalidierung
+Wenn Eingaben nur in einem bestimmten Format erfolgen dürfen, soll dies kenntlich gemacht werden, entweder durch
+* entsprechende Wahl bzw. Gestaltung der Formularfelder,
+* eine "intelligente" Interpretation der Eingaben (z.B. Erkennung von 15 oder 1500 als Uhrzeit 15:00 Uhr) oder
+* Hinweise beim Eingabefeld [siehe Hinweistexte](#Hinweistexte).
+* Verwendung entsprechender Input-Types (siehe [Eingabevalidierung](Howto/Eingabevalidierung))
+
+Die Eingabevalidierung soll, wenn möglich, direkt nach Verlassen des jeweiligen Eingabefeldes erfolgen. Zu jedem nicht ausgefüllten Pflichtfeld bzw. zu jedem sonstwie falsch ausgefüllten Eingabefeld soll der Korrekturhinweis direkt bei dem jeweiligen Eingabefeld erfolgen, so dass die Aufmerksamkeit des Benutzers direkt auf die noch zu vorzunehmenden bzw. zu korrigierenden Eingaben gelenkt werden.
+
+Weitere Informationen: [Eingabevalidierung](Howto/Eingabevalidierung)
+
+## Buttons
+Der Button zum Abschicken/Speichern/Übernehmen der eingegebenen Daten ("primäre Aktion") sollte linksbündig mit den Formularfeldern abschließen und sich direkt unterhalb des Formulars im `<footer>`-Element befinden. Damit wird deutlich, welche Daten durch einen Klick auf diesen Button übernommen werden.
+
+Ein Button zum Abbrechen oder Zurücksetzen ("sekundäre Aktion") soll vermieden werden. Wenn er erforderlich ist, soll er sich visuell von dem Button für die primäre Aktion unterscheiden.
+
+```php
+<form class="default" ...>
+...
+ <footer>
+ <?= \Studip\Button::createAccept(_("Speichern")) ?>
+ <?= \Studip\Button::createCancel(_("Abbrechen")) ?>
+ </footer>
+</form>
+```
+
+
+
+Attach:formfooter2015.png
+
+
+
+
+* TODO: Genauere Vorgaben für die Gestaltung von Buttons für sekundäre Aktionen formulieren
+
+#### Ausnahme: Buttons bei Wizards
+* wo sollten die Buttons für "zurück" und "weiter" bei mehrseitigen Formularen platziert werden
+ ** zentriert ausgerichtet, wie groß der Abstand zwischen beiden Buttons?
+
+** bei längeren Formularen (,die über eine Bildschirmseite gehen): Buttons "verdoppeln", also oben und unten auf der Seite anzeigen z. B. "zurück" und "weiter" Buttons
+
+* http://patternry.com/p=multiple-page-wizard/
+* weitere Recherche zu Buttons bei Wizards
+ ** Attach:labelsonform.pdf
+ ** Quelle: http://de.slideshare.net/cjforms/labels-and-buttons-on-forms/
+
+
+
+### Weiterführende Informationen
+
+#### Allgemein
+
+* Cheat Sheet For Designing Web Forms http://uxdesign.smashingmagazine.com/2011/10/07/free-download-cheat-sheet-for-designing-web-forms/ Attach:formsheet.pdf
+* http://uxdesign.smashingmagazine.com/2011/11/08/extensive-guide-web-form-usability/
+* http://www.formsthatwork.com/Articles
+* http://www.slideshare.net/cjforms/labels-and-buttons-on-forms
+* [Paper](http://www.intechopen.com/download/pdf/10814) "Simple but Crucial User Interfaces in the World Wide Web: Introducing 20 Guidelines for Usable Web Form Design"
+
+#### Placeholder
+* http://mentalized.net/journal/2010/08/05/dont_use_placeholder_text_as_labels/
+* http://dev.w3.org/html5/spec/single-page.html#the-placeholder-attribute
+* http://laurakalbag.com/labels-in-input-fields-arent-such-a-good-idea/
+
+
+## Checkboxen
+
+### Verwendung
+Checkboxen werden verwendet, um Optionen zu aktivieren bzw. zu deaktivieren.
+
+### Aussehen
+* Checkboxen sollten möglichst untereinander angeordnet werden. Dadurch können Sie einfacher gelesen werden.
+* Die Bezeichnung ist rechts vom Kästchen zu platzieren.
+* Kästchen und Bezeichnung sind linksbündig untereinander anzuordnen.
+
+### Beschriftung
+Negative Beschriftungen sollten vermeiden werden:
+* markierte Checkboxen aktivieren Einstellungen und deaktivieren diese nicht
+
+```html
+ <form ... >
+ <fieldset>
+ <legend>Beschriftung</legend>
+
+ <fieldset>
+ <legend>Checkboxengruppe</legend>
+ <input class="studip_checkbox" id="cb1" type="checkbox" name="cb" value="1">
+ <label for="cb1">Antwortmöglichkeit 1</label>
+ <input class="studip_checkbox" id="cb2" type="checkbox" name="cb" value="2">
+ <label for="cb2">Antwortmöglichkeit 2</label>
+ <input class="studip_checkbox" id="cb3" type="checkbox" name="cb" value="3">
+ <label for="cb3">Antwortmöglichkeit 3</label>
+ </fieldset>
+ ...
+ </fieldset>
+ </form>
+```
+### Reihenfolge der Checkboxen
+Wenn mehrere Checkboxen auf einer Seite vorhanden sind, sollten diese in einer logischen Reihenfolge aufgelistet sein, z. B. die Optionen zuerst, die am häufigsten verwendet werden.
+
+## Radio Buttons [#RadioButton](#RadioButton)
+
+### Verwendung
+Mit Hilfe von Radio Buttons können Nutzer genau eine Option von sich gegenseitig ausschließenden Alternativen wählen, z. B. E-Mail als Text oder in HTML versenden.
+
+Wenn es mehr als vier/sechs Optionen gibt, ist eine [Drop-Down-Liste](Visual-Style-Guide#DropDown) die bessere Wahl.
+
+### Verhalten
+Wenn möglich, sollte eine sinnvolle Default-Option vorausgewählt sein.
+
+```html
+<form class="default" ... >
+ <fieldset>
+ <legend>Beschriftung</legend>
+
+ <fieldset>
+ <legend>Checkboxengruppe</legend>
+ <input class="studip_checkbox" id="cb1" type="checkbox" name="cb" value="1">
+ <label for="cb1">Antwortmöglichkeit 1</label>
+ <input class="studip_checkbox" id="cb2" type="checkbox" name="cb" value="2">
+ <label for="cb2">Antwortmöglichkeit 2</label>
+ <input class="studip_checkbox" id="cb3" type="checkbox" name="cb" value="3">
+ <label for="cb3">Antwortmöglichkeit 3</label>
+ </fieldset>
+ ...
+ </fieldset>
+ </form>
+```
+
+#### Aussehen
+Radio Buttons sollten möglichst untereinander angeordnet werden. Dadurch können Sie leichter überflogen werden.
+Die Bezeichnung sollte rechts daneben sein.
+
+## Drop-Down Listen [#DropDown](#DropDown)
+
+Mit Hilfe von Drop-Down Listen können Nutzer genau eine Option aus zwei oder mehreren sich gegenseitig ausschließenden Optionen wählen. Sie werden anstelle von [Radio Buttons](Visual-Style-Guide#RadioButtons) für lange Listen mit Optionen verwendet.
+
+### Sortierung der Optionen
+Die Optionen sollten aufgabenlogisch oder natürlich angeordnet werden z. B. bei Wochentagen zuerst Montag, Dienstag. Falls es keine logisch sinnvolle Reihenfolge gibt, sollten die Optionen alphabetisch (bzw. alphanumerisch) angeordnet werden.
+
+http://uxmovement.com/forms/stop-misusing-select-menus
+
+### Verhalten
+Drop-Down Listen sollten möglichst einen voreingestellten Wert haben.
+
+## List Boxen
+### Verwendung
+List Boxen können als Alternative zu einer Reihe von [Radio Buttons](Visual-Style-Guide#RadioButton), die es ermöglichen, genau eine Option aus einer Reihe von sich gegenseitig ausschließenden Optionen zu wählen. Oder als eine Alternative zu [Checkboxen](Checkboxen) dienen, die es ermöglichen, eine beliebige Anzahl von Auswahlmöglichkeiten, aus einer Liste von Optionen auszuwählen. Sie benötigen weniger Platz auf dem Bildschirm als eine Liste von Radio-Buttons oder Checkboxen.
+
+List Boxen sollten nur sehr sparsam verwendet werden.
+
+## Datumseingaben
+* Veranstaltungstermin anlegen/bearbeiten
+* Termin im Terminkalender anlegen
+* Zeitbereich für Export im Terminkalender definieren
+* Regelmäßige Zeit anlegen/bearbeiten
+* Anzuzeigendes Datum im Belegungsplan definieren
+* Ressourcenbelegung eintragen/bearbeiten
+* Gültigkeitsdauer von
+ * News
+ * Votings
+ * Evaluationen
+* Anmeldezeitraum für Veranstaltungen definieren
+* Eigene "Veranstaltung" in Stundenplan eintragen
+* generische Datenfelder vom Typ "Datum"?
+
+# Informationspräsentation
+
+## Audio / Video
+
+## Lightbox
+
+Einfache Bildergallerien können in Stud.IP erzeugt werden, indem eine Vorschau des Bilds eingebunden und verlinkt wird. Dieser Link erhält das Attribut `data-lightbox`, wodurch das verlinkte Bild anschliessend in einer Dialog-ähnlichen "Lightbox" angezeigt wird. Sollen mehrere Bilder zusammengefasst werden, so ist das Attribut `data-lightbox` aller verlinkten Bilder mit dem eindeutigen Namen der entsprechenden Lightbox zu füllen, bspw. `data-lightbox="blubber"`.
+
+## Tabellen
+
+Stud.IP verfügt über ein einheitliches und einfach gehaltenes Tabellenlayout, das für alle tabellarischen Darstellungen verwendet werden soll. Kernelemente sind dabei ein sehr einfach zu verwendendes CSS und eine angenehme und unaufdringliche grafische Gestaltung.
+
+Eine Tabelle ist ungefähr so aufgebaut, wie in diesem Beispiel der Teilnehmerseite:
+
+![image](../assets/30d74c57a521f139c0050de6d55866a7/image.png)
+
+### Aufbau & Elemente
+Jede Tabelle setzt sich zusammen aus einer Beschriftung (Label) für die gesamte Tabelle, die Kopfzeile mit den Spaltenbeschriftungen, optionale Trenner-Zeilen, um Segmente in Tabellen gegeneinander abzugrenzen, eine Fußzeile und die normalen Tabellenzeilen. Tabellen selbst sind transparent, die Hintergrundfarbe (in Stud.IP einfaches Weiß) scheint durch.
+
+Grundsätzlich bauen sich die Spalten wie folgt auf:
+
+* Bereich für Bulk-Aktionen: Wenn Bull-Aktionen vorgesehen sind, nehmen diese die erste Spalte auf. Die erste Spalte besteht in diesem Fall aus Chdeckboxen, in der Kopfzeile ist eine Checkbox für das Aktivieren aller Chechboxen vorzusehen.
+* Icon: Das passende Icon für das Objekt
+* Name/Bezeichnung: Der Name des Objektes, dass die Tabellenzeile repräsentiert. Üblicherweise ist der Name klickbar, wenn dadurch der Zugriff auf das Objekt ermöglicht wird (etwa Download im Dateibereich, Link in die Veranstaltung auf der Seite "Meine Veranstaltungen"
+* weitere Spalten mit Namen des Autors/Erstellers, weitere Metadaten eines Objektes
+* Aktionsspalte: Diese nimmt entweder bis zu drei Aktionselemente (in Form von Icons) auf, oder, wenn mehr als drei Aktionen möglich sind, das Aktionsmenu. Dieses hier ist hier definiert:
+
+Der Klick auf den Header einer Spalte sortiert diese, sofern sinnvoll möglich. Ein weiterer Klick kehrt diese Sortierung um.
+
+Aktionselemente finden sich außerdem an folgenden Stellen:
+
+* Elemente, die sich auf die gesamte Tabelle beziehen: Oberhalb der Tabelle (Label-Zeile) in Form von Icons/Aktionsmenu.
+* Elemente die sich auf eine Zeile beziehen: Pro Spalte auf der rechten Seite in Form von Icons/Aktionsmenu.
+* Elemente, die sich auf ausgewählte Zeilen beziehen: Im Footer der Seite mit dazu gehörigen Checkboxen auf der linken Seite in jeder Tabellenzeile (Die Checkbox in der Tabellenkopfzeile neben den Überschriften der Spalten markiert alle sichtbaren Zeilen der Tabelle).
+
+### CSS
+
+Das folgende Beispiel verdeutlicht eine simple Tabelle, die nach aktuellen Vorgaben aufgebaut ist:
+
+```html
+<table class="default">
+ <caption>
+ <span class="actions">
+ <!-- Bereich für Aktions-Icons, die die gesamte Tabelle umfassen -->
+ </span>
+ TutorInnen
+ </caption>
+ <colgroup>
+ <col width="20">
+ <col>
+ <col width="80">
+ </colgroup>
+ <thead>
+ <tr class="sortable">
+ <th>Nr.</th>
+ <th>Nachname, Vorname</th>
+ <th style="text-align: right">Aktionen</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td style="text-align: right">01</td>
+ <td>Kater, Cornelis</td>
+ <td>
+ <!-- Bereich für Aktions-Icons, die die Zeile Tabelle umfassen -->
+ </td>
+ </tr>
+ </tbody>
+ <tfoot>
+ \\
+ <tr>
+ <td colspan="3">
+ <!-- Bereich für Aktions-Icons, die die gesamte Tabelle umfassen -->\\
+ </td>
+ </tr>
+ </tfoot>
+</table>
+```
+
+
+Das Beispiel zeigt, das relativ wenige CSS-Stile verwendet werden müssen, um das Standard-Design von Stud.IP zu erhalten. Die Tabelle wird als Default-Klasse definiert, dadurch ergibt sich bereits der größte Teil des Aussehens.
+Im Beispiel nicht gezeigt wird ist eine eigene Klassen namens `collapsable`, die in einem `<tboby>`-Element zugewiesen wird, wenn Tabellen in sich gegliederte (und auch zuklappbare) Bereiche aufweisen.
+
+Weitere Hinweise:
+
+* Jede Tabelle muss ein Label führend, das die Tabelle klar benennt
+* Weitere Gestaltungselemente sollen nicht eingeführt werden (ggf. bitte Rücksprache mit der GUI-Gruppe halten)
+* Tabellenbereiche können auf- und zuklappbar gestaltet werden
+* Hierarchische Tabellenstrukturen sind zukünftig nicht mehr vorgesehen. Stattdessen soll, dass das Auswählen eines Knotens (der in der Regel einer Zeile entspricht) die nächste Ebene springen, die dann vollständig angezeigt wird (übergeordnete Ebene werden ausgeblendet). Beispiel dafür ist die Umsetzung des Dateibereiches ab der Version Stud.IP 4.0
+* Dieses neue Tabellenlayout gilt lediglich für rein tabellarische Darstellungen. Systembereiche, die bisher Tabellen genutzt haben, um den allgemeinen Seitenaufbau zu beeinflussen, dürfen nicht au die diese Stile umgestellt werden. Hier empfiehlt sich, entweder das bestehe Aussehen (zunächst) beizubehalten oder ohne HTML-Tabellenstrukturen neue aufzubauen. In der Regel sind Forms hier die bessere Alternative.
+
+
+# Elementlisten
+
+## Stand der Dinge
+In Stud.IP werden verschiedenste Objekte/Elemente in Listenform ausgegeben: Personen (z.B. Veranstaltungsteilnehmer), Veranstaltungen (z.B. auf der Seite »Meine Veranstaltungen«), Einrichtungen, News, Votings, Studiengänge und vieles mehr.
+
+Durch die konsequente Verwendung der neuen Stud.IP-Tabellen ist die Einheitlichkeit der Darstellung bereits stark verbessert worden. Einige alte Seiten müssen noch auf das neue Design umgestellt bzw. templateisiert werden (Stand August 2015)
+
+
+## Darstellung
+* Die einzelnen Elemente einer Liste werden in einzelnen Zeilen **untereinander** angeordnet.
+* In den Zeilen einer Elementliste findet **kein Zeilenumbruch** statt.
+* Jede Zeile enthält den Namen des Elements bzw. andere, möglichst wenige Informationen, die das Element identifizieren (bei Terminen z. B. Datum und Uhrzeit).
+* Sofern die Elemente einander hierarchisch untergeordnet werden sollen, wird diese **Hierarchie durch Einrückungen** dargestellt
+* als Design gilt das aktuelle Tabellendesign von Stud.IP (siehe dort).
+* Jede Elementliste sollte **tabellarisch** aufgebaut sein. Dies erhöht die Lesbarkeit der Inhalte.
+ * Zu einer Tabelle gehört ein **Tabellenkopf**, der für jede Spalte der Tabelle eine Überschrift liefert.
+ * Die **horizontale Ausrichtung** innerhalb einer jeden Spalte ist abhängig von den Inhalten und deren Zweck sinnvoll zu wählen.
+ * Die Ausrichtung in Tabellenkopf und Tabellenkörper sollte gleich sein.
+ * Bei langen Elementlisten kann es sinnvoll sein, die Liste durch **Zwischenüberschriften** zu unterbrechen. In solchen Fällen ist der Tabellenkopf über jeder Teil-Liste zu wiederholen.
+* Wenn die Elementliste für die Darstellung auf einer einzigen Seite zu lang ist, ist eine **Paginierung** vorzusehen.
+ * Zum Blättern werden rechts unterhalb der Elementliste die Seiten angezeigt, auf die sich die gesamte Liste verteilt.
+ * Bei einem Klick auf eine Seitennummer gelangt man auf die jeweilige Seite
+ * Zusätzlich gibt es links neben den Seitennummern einen Link "zurück" (außer auf Seite 1) sowie rechts neben den Seitennummern den Link "weiter" (außer auf der letzten Seite).
+ * (zu weiteren Details siehe Paginierung auf score.php)
+
+## Aktionen
+* Einzelaktionen
+ * Einzelaktionen sind Aktionen, die sich auf einzelne Elemente einer Liste beziehen.
+ * Bei Einzelaktionen ist zwischen Standardaktionen und erweiterten Aktionen zu unterscheiden.
+ * Standardaktionen sind Aktionen, die auf die meisten Arten von Listenelementen angewendet werden können. Sie werden durch Icons (nicht durch Buttons!) in der Überschriftszeile des Listenelements ausgelöst. Standardaktionen sind
+ * Löschen
+ * Auf- und Zuklappen
+ * Sortieren/Reihenfolge ändern
+ * Erweiterte Einzelaktionen sind Aktionen, die über die Standardaktionen hinausgehen. Sie sind nur für einzelne Arten von Listenelementen anwendbar und/oder erfordern umfangreichere Formulare o.ä., die allein schon vom Platz her nicht in die Überschriftszeile passen würden. Beispiele für erweiterte Aktionen sind das Einstellen der Laufzeit von Evaluationen oder das Buchen eines Raumes für einen Termin.
+ * Um erweiterte Einzelaktionen ausführen können, muss der Benutzer das jeweilige Listenelement erst aufklappen. Unter dem Listenelement öffnet sich dann ein Bereich, in dem die Interaktionselemente angezeigt werden, mit denen die Aktion ausgeführt werden kann.
+ * Löschen
+ * Das Löschen eines Listenelements wird durch den Klick auf ein Mülleimersymbol rechts in der Titelzeile des Listenelements durchgeführt (nicht durch einen Löschen-Button, der erst nach dem Aufklappen sichtbar wird).
+ * Auf- und Zuklappen
+ * Oft braucht man die Möglichkeit, ein Listenelement aufzuklappen. Dies ist regelmäßig dann der Fall, ...
+ * ... wenn man zu einem Element zusätzliche Informationen einblenden möchte, die nicht in die Überschriftszeile des Listenelements passen (z.B. Informationen über Teilnehmer einer Veranstaltung)
+ * ... wenn man umfangreiche Bearbeitungsmöglichkeiten bereitstellen möchte, die nicht in die Überschriftszeile des Listenelements passen (z.B. bei Umfragen oder Evaluationen)
+ * ... wenn man weitere Listenelemente einblenden möchte, die dem Listenelement hierarchisch untergeordnet sind (z.B. in der Veranstaltungshierarchie)
+ * ... wenn man Objekte einblenden möchte, die in dem Listenelement enthalten sind (z.B. Personen in einer Statusgruppe)
+ * ... wenn man Objekte einblenden möchte, die dem Listenelement auf die andere Art zugeordnet sind (z.B. Einzeltermine eines regelmäßigen Termins)
+ * Zum Auf- und Zuklappen ist ein ">"-Icon am linken Ende der Titelzeile anzuzeigen. Ein Klick darauf klappt das jeweilige Listenelement auf. Das Icon wird dabei gegen eines ausgetauscht, dessen Spitze nach unten zeigt.
+ * Bei aktiviertem JavaScript sollte das Auf- und Zuklappen ohne Page Reload realisiert werden.
+ * Grundsätzlich sollte es durch das Aufklappen eines Listenelements möglich sein, die Grundinformationen zu ändern, die in der Titelzeile des Listenelements enthalten sind. Dazu wird in der Titelzeile dort, wo im zugeklappten Zustand die jeweilige Information/Eigenschaft ausgegeben wird, ein entsprechendes Formularfeld angezeigt, das mit den aktuellen Werte gefüllt ist.
+ * Manchmal ist es sinnvoll, alle Listenelemente auf einmal auszudrucken. Dies wird durch ein Icon realisiert, das einen Pfeil nach oben und einen nach unten zeigt. Dieses Icon wird in einer Zeile zwischen dem Tabellenkopf und dem Tabellenkörper eingefügt.
+ * Sortieren/Reihenfolge ändern
+ * Eine Elementliste sollte grundsätzlich sortierbar sein. Dadurch wird es den Benutzern ermöglicht, die Inhalte ihren Wünschen und Nutzungszielen entsprechend darzustellen. Von einer Sortierbarkeit kann bei hierarchischen oder geschachtelten Listen verzichtet werden.
+ * Die Liste sollte nach den Kriterien sortierbar sein, die durch die Überschriften im Tabellenkopf repräsentiert sind, sofern dies sinnvoll möglich ist.
+ * Beim ersten Aufruf der Liste sollte diese bereits nach einem sinnvollen Kriterium sortiert sein.
+ * Nach welchem Kriterium und in welcher Richtung (auf- oder absteigend) eine Liste sortiert ist, wird durch ein kleines blaues Dreieck rechts neben der jeweiligen Überschrift im Tabellenkopf angezeigt. Eine aufsteigende Sortierung wird durch ein nach oben zeigendes Dreieck, eine absteigende Sortierung durch ein nach unten zeigendes Dreieck angezeigt.
+ * Die Sortierung der Liste erfolgt durch den Klick auf die jeweilige Überschrift im Tabellenkopf oder auf das gelbe Dreieck. Ist die Liste bereits nach dem
+ Kriterium sortiert, das man anklickt, so wird die Reihenfolge der Sortierung umgekehrt.
+ * Die Möglichkeit des Klicks auf den Namen wird durch blaue Schrift (Standardfarbe für Links) dargestellt, die Farbe entspricht der Farbe des Dreiecks.
+ * In bestimmten Fällen kann es sinnvoll sein, die Reihenfolge der Elemente manuell festzulegen (also keine Sortierung nach einem Kriterium).
+ * Hat der Benutzer in seinem Brwoser JavaScript aktiviert, so sollte er die Möglichkeit haben, die Reihenfolge per Drag and Drop festzulegen.
+ * (spezifizieren)
+ * Wenn im Browser des Benutzers JavaScript ausgeschaltet ist, sollten gelbe Sortierpfeile im rechten Bereich der jeweiligen Zeile zur Verfügung stehen, mit denen die Reihenfolge festgelegt werden kann. Diese sind in zwei Spalten angeordnet: Die Pfeile, die nach unten zeigen, befinden sich in der linken Spalte, die Pfeile, die nach oben zeigen, in der rechten Spalte. Die oberste Zeile enthält nur einen Pfeil nach unten, die unterste Zeile nur einen pfeil nach oben.
+ * Wenn die Elementliste sehr lang ist, kann es für den Benutzer schwierig werden, einzelne Listenelemente an eine weiter entfernte Position innerhalb der Liste zu befördern. In so einem Fall können dem Benutzer zusätzlich Optionsfelder und gewinkelte Pfeile zur Verfügung gestellt werden, mit denen einzelne Listenelemente ausgewählt und an eine bestimmte Stelle einsortiert werden können (Beispiel: Nutzerverwaltung in Einrichtungen).
+* Sammelaktionen
+ * Eine Sammelaktion ist eine Aktion, die auf mehrere Listenelemente gleichzeitig angewendet wird.
+ * Das Auswählen der Listenelemente für eine Sammelaktion erfolgt mittels Checkboxes links in den Titelzeilen der Listenelemente.
+ * Unterhalb der Elementliste steht eine Dropdown-Box zur Verfügung, aus der man die gewünschte Aktion auswählen kann. Mit einem Klick auf einen Button "OK" wird die Sammelaktion ausgeführt.
+ * Zusätzlich stehen in der Dropdown-Box Optionen zur Veränderung der Auswahl zur Verfügung (mindestens "alle auswählen", "keine auswählen" und "Auswhl umkehren").
+
+## Auf- und Zuklappen
+
+* Sofern es zu einem Element mehr Informationen gibt, als man es in einer Textzeile darstellen kann, soll dies durch Auf- und Zuklappen des Elements dargestellt werden. Hierzu ist ganz links in der entsprechenden Zeile ein nach rechts weisendes Dreieck auszugeben. Ein Klick auf dieses Dreieck bewirkt, dass unterhalb des gewählten Elements die Detailinformationen eingeblendet werden, ggf. mit Möglichkeiten, diese zu bearbeiten. Die Elemente unter dem aufgeklappten Element rutschen entsprechend weiter nach unten. Das Dreieck, dessen Anklicken das Aufklappen bewirkt hat, weist nach unten, wenn das Element aufgeklappt ist. Ein weiterer Klick darauf "schließt" das aufgeklappte Element wieder. Das Auf- und Zuklappen soll zusätzlich durch Klicken auf den Namen des Elements ermöglicht werden.
+* Wie soll das Bearbeiten der Elementeigenschaften umgesetzt werden? Soll es immer identisch funktionieren? Falls nicht: Welche Abweichungen sollen erlaubt sein, und unter welchen Bedingungen sollen sie erlaubt sein?
+ * Variante 1: Beim Aufklappen bleibt die Titelzeile unverändert, sämtliche Informationen (also auch die in der Titelzeile eingeblendeten) werden unterhalb der Titelzeile in Formularfelder eingeblendet und können mit einem Klick auf den Button "übernehmen" gespeichert werden. Temporär widersprechen sich also die Angaben aus der Titelzeile und dem Formularfeld, in dem man diese Information gerade ändert. (Beispiel: Ablaufplan, Gruppen/Funktionen in Einrichtungen)
+ * Variante 2: Beim Aufklappen ist zunächst noch nichts bearbeitbar. Dazu muss man erst auf den Button "bearbeiten" klicken (der erst durch Aufklappen sichtbar wird). Die in der Titelzeile enthaltenen Attribute werden in der Titelzeile bearbeitbar gemacht; unterhalb dieser werden zuätzliche Informationen bearbeitbar gemacht. Ein Klick auf den Button "übernehmen" (unterhalb der bearbeitbaren Informationen) speichert alles. (Beispiel: Dateibereich, Forum)
+ * Variante 3: Wie Variante 2, aber die Informationen aus der Titelzeile werden nicht in der Titelzeile selbst, sondern (wie in Variante 1) unterhalb der Titelzeile bearbeitbar gemacht. (Beispiel: Literaturverwaltung)
+ * Variante 4: Durch Aufklappen werden bereits alle Informationen bearbeitbar gemacht. Die Information aus der Titelzeile wird daselbst bearbeitbar gemacht. Ein Klick auf den Button "übernehmen" unterhalb sämtlicher Informationen speichert die Änderungen und schließt das Element. (Beispiel: Einzeltermin auf Raumzeitseite)
+ * Variante 5: Durch Aufklappen (oder durch Klick auf ein Bearbeiten-Icon in der Titelzeile) wird die Information aus der Titelzeile bearbeitbar gemacht und durch Klick auf den Button "übernehmen" (der sich innerhalb der Titelzeile befindet) gespeichert. Gleichzeitig wird das Element zugeklappt. (Beispiel: regelmäßige Zeit auf Raumzeitseite)
+ * Variante 6: Durch Aufklappen werden alle Informationen bearbeitbar gemacht. Die Information aus der Titelzeile wird daselbst bearbeitbar gemacht, zusätzlich werden unterhalb der Titelzeile weitere Informationen bearbeitbar eingeblendet. Der Klick auf den Button "übernehmen" (unterhalb sämtlicher Informationen) speichert die Änderungen und schließt das Element. (Beispiel: Gruppierungs- und Fragenblöcke in Evaluationen)
+ * Variante 7: Durch Aufklappen werden Zusatzinformationen und -eigenschaften eingeblendet und bearbeitbar gemacht und durch Klick auf den Button "übernehmen" gespeichert, wobei das Element gleichzeitig geschlossen wird. Die Information aus der Titelzeile kann hierbei jedoch nicht geändert werden. Dazu muss man auf den Button "bearbeiten" in der Titelzeile klicken. Dies führt zu einer neuen Seite, auf der man die Information aus der Titelzeile, aber auch eine Reihe weiterer Informationen/Eigenschaften des Elements bearbeiten kann. (Beispiel: Evaluationen und Evakuationsvorlagen)
+ * Variante 8: Wie Variante 7, nur wird hier durch bloßes Aufklappen nichts bearbeitbar gemacht. Vielmehr muss man zum Bearbeiten sämtlicher Informationen auf den Button "bearbeiten" in der Titelzeile klicken, wodurch man auf eine andere Seite gelangt. (Beispiel: Votings)
+ * Variante 9: Das Element ist nicht aufklappbar. Um die Elementeigenschaften zu ändern, klickt man auf den Button "bearbeiten" in der Titelzeile. Dieser führt auf eine neue Seite, auf der man sämtliche Eigenschaften bearbeiten kann. Ein Klick auf den Button "übernehmen" (oder auf einen Text-Link "zurück" o. ä.) führt zurück zur vorherigen Seite. (Beispiel: News, Klausuren und Übungsblätter in Vips)
+ * Variante 10: In der Titelzeile befindet sich ein Bearbeiten-Icon. Ein Klick darauf verändert die Hintergrundfarbe der Titelzeile, um anzuzeigen, dass es sich im Bearbeiten-Modus befindet. Oben auf der Seite wird ein bereits bestehendes Formular (das zum Festlegen der Eigenschaften neu anzulegender Elemente verwendet wird) durch ein ähnliches Formular ersetzt, das mit den Werten des ausgewählten Elements befüllt wird, wodurch man sie bearbeiten kann. Ein Klick auf den Button "speichern" innerhalb dieses Formulars übernimmt die Änderungen, setzt die Hintergrundfarbe der Titelzeile sowie das Formular zurück. (Beispiel: Gruppen/Funktionen in Veranstaltungen, Gruppenverwaltung in Vips funktioniert ähnlich)
+
+## Löschen
+
+Sofern es möglich sein soll, einzelne Listenelemente zu löschen, so soll dies durch ein Mülleimer-Icon symbolisiert werden. Dieses Icon befindet sich ganz rechts in der jeweiligen Zeile. Ein Klick darauf bewirkt, dass das Element gelöscht bzw. aus dem jeweiligen abstrakten "Container" (Veranstaltung, Statusgruppe usw.) entfernt wird. Das Mülleimer-Icon steht sowohl im zu- als auch im aufgeklappten Zustand zur Verfügung.
+
+## Sortieren
+
+### Frei
+* Sofern die Reihenfolge der Elemente dauerhaft geändert werden soll (wenn also nicht nur die momentane Darstellung von z. B. Suchergebnissen verändert werden soll), sind Interaktionselemente vorzusehen, mit denen man dies bewerkstelligen kann.
+* Beispiele für sortierbare Elemente: Personen, Dateien, Forumsbeiträge, Veranstaltungen, Literatur, Termine, Themen, Ressourcen, Statusgruppen, News, Votings, Evaluationen, Nachrichten, Raumanfragen, Studienbereiche, ...
+* JavaScript aktiviert
+ * Drag and Drop: Hierfür ist ein Anfasser-Icon am linken Rand der jeweiligen Zeile anzuzeigen. (Befindet sich dort ein Auf- und Zuklapp-Dreieck, so wird der Anfasser direkt rechts daneben angezeigt.) Klicken und Festhalten dieses Icons bewirkt, dass man die jeweilige Zeile in gerader Linie nach oben oder unten bewegen und durch Loslassen an einer andere Stelle einsortieren kann. Dieses Drag and Drop steht sowohl im auf- als auch im zugeklappten Zustand zur Verfügung.
+* JavaScript deaktiviert
+ * Sortierpfeile: Anstelle der Riffelung sind gelbe Doppeldreiecke anzuzeigen. Das obere und untere Element einer sortierbaren Liste enthält nur ein Doppeldreieck, das nach unten bzw. nach oben zeigt. Alle anderen Zeilen enthalten jeweils zwei Doppeldreiecke, von denen einer nach oben, der andere nach unten zeigt. Ein Klick auf ein Doppeldreieck verschiebt das jeweilige Element um eine Stelle nach oben bzw. unten, während das vormals darüber liegende Element die Position des verschobenen Elements einnimmt.
+ * Radiobuttons plus Winkelpfeile: In bestimmten Kontexten ist es mitunter erforderlich, mehrere Elemente nacheinander um eine große Anzahl von Positionen zu verschieben. Hierfür kann eine alternative Sortierfunktion angeboten werden. Bei dieser Lösung markiert man einen Eintrag mittels eines Radiobuttons (links vom Auf- und Zuklapp-Dreieck) und wählt durch Anklicken eines Icons (in Form eines gewinkelten Pfeils links des jeweiligen Radiobuttons) die Stelle, an welcher der gewählte Eintrag einsortiert werden soll.
+### nach Kriterium
+* Manchmal möchte man Listenelemente nach einem bestimmten kriterium sortieren (Name, Dateigröße, Datum usw.). Hierzu gelten folgende Regeln:
+ * Listenelemente können nur nach Kriterien sortiert werden, deren Werte an der Oberfläche sichtbar sind. Eine Liste von Dateien sollte also zum Beispiel nicht nach Datum sortiert werden können, wenn das Datum der Datei nicht auch eingeblendet ist.
+ * Das Sortieren erfolgt durch das Klicken auf eine Spaltenüberschrift, unterhalb der die Werte dieses Kriteriums für jedes Listenelement aufgeführt ist.
+ * Grundsätzlich soll es möglich sein, durch mehrfaches Klicken auf eine Spaltenüberschrift die Elementliste aufsteigend und absteigend nach dem jeweiligen Kriterium zu sortieren.
+### Probleme/Fragen beim Sortieren:
+* Wie sortiert man innerhalb hierarchischer Gliederungen die Elemente verschiedener Ebenen (Beispiel: Gruppen/Funktionen in Veranstaltungen)?
+* Wie geht man mit Paginierung um? Sortiert man die gesamte Liste oder nur die sichtbaren Elemente?
+
+# Sprache (Sprachstil)
+
+## Sprache
+* Allgemein: Keine unnötigen Texte, schon gar keine, die dem Nutzer erzählen, was er auf dieser Seite tun oder lassen kann
+* Duzen vs. Siezen
+* ...
+
+## Schreibstil
+
+* TODO: Regeln übernehmen und übersetzen http://developer.android.com/design/style/writing.html
+
+## Terminologie
+* Allgemein: Keine Fachausdrücke: Speak the User's Language
+* "Vokabelliste"
+ * Veranstaltung
+ * Inhaltselemente
+ * Ankündigungen
+ * ...
+
+# Meldungen
+
+Wo wird Rückmeldung angezeigt?
+* Nutzer befindet sich unten auf der Seite
+* da macht oben die Infomeldung anzuzeigen, keinen Sinn
+
+Rückfrage bei Löschen von Objekten, ob wirklich gelöscht werden soll?
+* derzeit unterschiedlich in Stud.IP
+
+
+
+## Sicherheitsabfragen
+* als modalen Dialog?
+* teilweise werden diese nicht als Dialog, sondern auf der Seite angezeigt
+
+
+Quelle: http://developer.android.com/design/patterns/confirming-acknowledging.html
+
+## Weiterführende Links
+* http://patternry.com/p=feedback-messages/
+* http://www.userfocus.co.uk/articles/errormessages.html
+* http://uxmag.com/articles/are-you-saying-no-when-you-could-be-saying-yes-in-your-web-forms
+
+Buch:
+Designed for Use Chapter 6 über Text Usability
+
+# Sonstiges
+
+## Logos
+Für Stud.IP existiert seit 1999 ein feststehendes Logo in zwei Varianten (mit Bildmarke und nur Schrift), das im Jahr 2014 mit Erscheinen der Version 3.0 modernisiert wurde.
+
+Varianten bis 2014
+
+![image](../assets/1ce06d185917489aa6f3da79f9a491d6/image.png)
+
+![image](../assets/8fcb015158becfc136f6d2db84ef27bf/image.png)
+
+Varianten ab 2014/Version 3.0
+
+![image](../assets/c061aa06f3acdf6003dc54bd1e677ebe/image.png)
+
+![image](../assets/829b531d3dbd3e2bde37443f1394f821/image.png)
+
+Das Download-Paket mit allen Logo-Varianten kann dauerhaft unter folgender Adresse herunter geladen werden:
+
+http://bit.ly/studip-logo
+
+In diesem Paket enthalten sind die beiden Grundformen in verschiedenen Farbvarianten enthalten sowie Erweiterungen (Logo für Stud.IP e.V., Stud.IP mobile usw.). Das Paket wird regelmäßig aktualisiert, wenn weitere Varianten vom Grafiker erstellt werden.
+
+### Verwendung
+
+Generell soll zukünftig die Variante mit Bildmarke in der farbigen Variante verwendet werden. Für dunkle Hintergründe gibt es eine Variante, die überwiegend in weiß gehalten ist. Nur in ausgewählten Fällen (zB. im Fließtext oder als Wasserzeichen) kann die Variante ohne Bildmarke und ggf. in der einfarbigen Version verwendet werden.
+
+## Suche
+## Stand der Dinge
+* Objekte, nach denen gesucht werden kann (und was man dann damit macht)
+ * Personen
+ * Adressbuch: zum Adressbuch hinzufügen; zu einer Gruppe im Adressbuch hinzufügen
+ * Globale Einstellungen: Globale Eigenschaften ändern
+ * Veranstaltungen: als Dozent/Tutor zu einer Veranstaltung hinzufügen
+ * Veranstaltungen: als Teilnehmer zu einer Veranstaltung hinzufügen
+ * Messaging: zur Empfängerliste hinzufügen
+ * allg. Personensuche: persönliche Homepage aufrufen, Nachricht schicken
+ * Einrichtungen: zur Mitarbeiterliste hinzufügen
+ * Ressourcenverwaltung: als lokalen/globalen Ressourcenadmin eintragen
+ * Veranstaltungen
+ * Veranstaltungssuche für Studis: zu einzelnen Veranstaltungen wechseln (= zur Übersichtsseite wechseln); persönliche Homepage einzelner Veranstaltungen aufrufen
+ * Veranstaltungssuche für Admins: Veranstaltung zum Bearbeiten auswählen, diverse "Batch-Aktionen" (Sichtbarkeit, Sperrebenen usw.) ausführen
+ * im Archiv: diverse Aktionen (Details aufrufen, Dateisammlung herunterladen, endgültig löschen, ...)
+ * Veranstaltungshierarchie: Veranstaltung(en) zu Studienbereichen hinzufügen
+ * Einrichtungen
+ * Zur Einrichtung in Stud.IP wechseln
+ * Zur Website der Einrichtung wechseln
+ * eine E-Mail an den Ansprechpartner schreiben
+ * Ressourcen
+ * diverse Aktionen (die man auch sonst an Ressourcen vornehmen kann: Belegungsplan aufrufen, Eigenschaften bearbeiten, ...)
+ * Bereiche (in News-, Voting- und Evaluationsverwaltung)
+ * Neues Voting/neuen Test in einem Bereich erstellen
+ * Bereich auswählen (um dort anschließend eine News zu erstellen/bearbeiten)
+ * Forenbeiträge
+ * diverse Aktionen (die man auch sonst an Forenbeiträgen vornehmen kann: antworten, zitieren, bearbeiten, ...)
+ * Öffentliche Evaluationsvorlagen
+ * zu den eigenen Evaluationsvorlagen hinzufügen
+ * Wikiseiten
+ * Wikiseiten aufrufen
+ * Literatur (= Einträge in Literaturlisten)
+ * Details aufrufen
+ * in Merkliste eintragen
+ * zum Eintrag im externen Katalog (OPAC) wechseln
+* Formulierung der Suchanfrage
+ * Variante 1: nur ein einzeiliges Textfeld
+ * Variante 2: Textfeld(er) plus weitere Formularfelder (z. B. Veranstaltungssuche, Personensuche, Ressourcensuche)
+
+* Auto-Complete
+ * ...
+
+* Auslösen der Suchanfrage
+ * Variante 1: Klick auf Icon "Lupe" (z. B. Suche nach Dozenten auf admin_seminare1.php, Suchen eines Wunschraums auf admin_room_request.php)
+ * Variante 2: Klick auf Button "Suche starten" (z. B. Suche nach Veranstaltungen auf sem_portal.php, Suche nach Ressourcen, Suchen im Archiv,
+ * Variante 3: Klick auf Button "suchen" (z. B. Suche nach Personen auf browse.php, Suche nach Literatur auf lit_search.php)
+
+## Mögliche Guidelines
+* Die Eingabe von Suchbegriffen erfolgt grundsätzlich in ein einzeiliges Texteingabefeld (input type="text").
+* Sofern das Suchformular nur aus diesem Textfeld besteht, wird die Suche durch Klicken auf ein rechts neben dem Textfeld angebrachtes Lupen-Icon ausgelöst.
+* Besteht das Suchformular aus mehreren Formularfeldern (z. B. zum Einschränken der Suchergebnisse), so wird die Suche grundsätzlich durch einen Button mit dem Text "Suche starten" ausgelöst.
+
+## Darstellung von Suchergebnissen
+
+### Stand der Dinge
+* **Variante 1:** Dropdownliste ersetzt Eingabefeld (Beispiele: Zuweisen eines Dozenten zu einer Veranstaltung auf admin_seminare1.php; Auswählen eines Wunschraums beim Formuliren einer Raumanfrage)
+* **Variante 2:** Aufklappbare (bzw. bereits aufgeklappte) Listenelemente (z. B. Ressourcensuche, Literatursuche)
+* **Variante 3:** Einfache Liste (z. B. Veranstaltungssuche sem_portal.php, Personensuche browse.php und_new_user_md5.php)
+* **Variante 4:** Aufgeklappte Elemente innerhalb einer hierarchischen Liste von ansonsten zugeklappten Elementen (Suche nach Einrichtungen institut_browse.php)
+* **Variante 5:** Mehrzeilige Selectbox (Freie Suche nach Personen in der Gruppenverwaltung in Einrichtungen, Gruppenverwaltung im Adressbuch)
+
+### Mögliche Guidelines
+* Oberhalb der Suchergebnisse soll die Anzahl gefundener Elemente ausgegeben werden.
+* Unterscheidung nach Verwendungszweck der Ergebnisse
+ * **Auswählen genau eines Elements der Ergebnisliste** (z. B. Zuordnung eines Dozenten zu einer Veranstaltung, Auswählen eines Wunschraumes in einer Raumanfrage)
+ * **Auswählen ggf. mehrerer Elemente der Ergebnisliste** (z. B. Zuordnung von Veranstaltungen zu Studienbereichen in der Studienbereichsverwaltung, Zuordnen von per Suche gefundenen Personen in der Gruppenverwaltung in Einrichtungen)
+ * **Anklicken genau eines Elements der Ergebnisliste, um es anzusteuern** (z. B. Veranstaltungssuche [sem_portal], Personensuche [browse.php])
+
+### Fragen/Ideen
+* Wie hängen die Guidelines zu den Suchergebnissen mit denen zu Elementlisten zusammen? M.a.W.: Wann sollen Suchergebnisse in Form von Elementlisten ausgegeben werden, für die dann automatisch die dort definierten Regeln greifen?
+ * Idee: Suchergebnisse werden grundsätzlich in Form von Elementlisten dargestellt. (Ggf. mit definierten Ausnahmen wie z.B. Dozentensuche auf admin_seminare1.php)
+* Wovon kann/sollte man die Art der Darstellung abhängig machen?
+ * Verwendungszweck der Suchergebnisse (s.o.)
+ * Art und Darstellung des Suchformulars, aus dem die Suchergebnisse stammen
+ * Zur Verfügung stehender Platz (z.B. innerhalb von auf- und zuklappbaeren Elementlisten, Beispiel: Personensuche innerhalb der Gruppenverwaltung in Einrichtungen)
+ * Art der gesuchten Elemente (Personen vs. Veranstaltungen vs. Ressourcen vs. ...)
+ * Vorhersagbarkeit der Treffermenge (iframe oder selectbox bei potenziell großen Treffermengen)
+ * %blue% Denkbar ist eine Matrix mit zwei Kriterien, in der für jede Kombination aus Kriterienausprägungen ein eigenes Set von Darstellungsregeln definiert ist
+* Blättern
+ * Soll (grundsätzlich bzw. nach definierten Kriterien) innerhalb der Suchergebnisse geblättert werden können?
+ * Wenn ja, wie soll das Blättern dargestellt werden? (Ggf. generelle Regeln für Blätterfunktion)
+### Einstellungen
+
+* gute Ideen hier: http://developer.android.com/design/patterns/settings.html
+
+# Pfade zu den Grafiken
+
+Generell werden alle Grafiken im `assets`-Ordner `images` im `public`-Ordner gespeichert. Diese wurden in 2.0 neu strukturiert und aufgeräumt. Somit ergibt sich dort folgende Struktur:
+
+| Pfad | Beschreibung |
+| ---- | ---- |
+| `public/assets/images/calendar` | Hier sind alle Hintergrundgrafiken drin, die für den Kalender und Stundenplan benötigt werden. |
+| `public/assets/images/crowns` | Die Kronen, die ein Benutzer als Auszeichnung bekommen kann |
+| `public/assets/images/header` | `deprecated` (Diese Icons werden noch gelöscht) |
+| `public/assets/images/icons` | Der neue Ordner für alle Icons, ausführliche Informationen siehe unten. |
+| `public/assets/images/infobox` | Alle Bilder mit abgerundeter Ecke in Schwarz-weiß für die Infoboxen |
+| `public/assets/images/languages` | Länder-Icons für die vorhanden Sprachen |
+| `public/assets/images/locale` | Hier sind alle sprachabhängigen Icons |
+| `public/assets/images/logos` | Hier liegen alle Logos, die irgendwo in Stud.IP verwendet werden |
+| `public/assets/images/vendor` | Hier werden Grafiken abgelegt, die zu anderen Paketen, Frameworks etc gehören und nicht von uns erstellt wurden (wie z.B. jQuery-UI) |
+
+# Icons
+
+Alle neuen Icons werden einheitlich in einem bestimmten Schema abgelegt. Dazu gehören Größe, Farbe, Zusatz und Name.
+
+Größen:
+* 16
+* 32
+
+Farben:
+* white
+* black
+* grey
+* green
+* blue
+* red
+* yellow
+
+Zusätze:
+* new
+* reload
+* add
+* remove
+* left
+* right
+* up
+* down
+
+**Will man Icons *anzeigen*, verwendet man einfach die Icon-API und muss sich nicht mit den obigen Spezifika beschäftigen.**
diff --git a/docs/docs/restapi/index.md b/docs/docs/restapi/index.md
new file mode 100644
index 0000000..67f1d4f
--- /dev/null
+++ b/docs/docs/restapi/index.md
@@ -0,0 +1,3223 @@
+---
+title: REST-API
+sidebar_label: Übersicht
+---
+
+:::danger Veraltetes Feature
+
+Die REST-API wurde zu Stud.IP v5.0 als deprecated gekennzeichnet und wird zur Version 6.0
+ausgebaut werden. Bitte verwenden Sie die [JSONAPI](../jsonapi).
+
+:::
+
+### Einführung
+
+Seit Version 3.0 steht mit der REST-API eine umfangreiche HTTP-basierte Schnittstelle für das Stud.IP-System zur Verfügung. Mit dieser können grundlegende Daten von Nutzern, und Veranstaltungen abgefragt werden. Zudem ermöglicht die API, Blubber-Nachrichten, Foren-Beiträge und Wiki-Seiten zu erstellen und abzurufen.
+
+### Administration der REST-API
+
+Die REST-API ist in Stud.IP standardmäßig nicht aktiviert. Um sie zu aktivieren muss ein Root-Benutzer auf der Administrationsseite eine Konfigurationsvariable umschalten und REST-Routen aktivieren.
+
+#### Anschalten der API
+
+Nachdem man sich als Root-Benutzer angemeldet hat, wählt man über die Stud.IP-Navigation nacheinander die Punkte Admin -> System -> Konfiguration an und klickt auf der Seite "Verwaltung von Systemkonfigurationen" den Bereich "global" an. Es erscheint eine Liste mit Konfigurationsvariablen. In dieser Liste bearbeitet man die Variable API_ENABLED und setzt diese im Dialog, der sich nach dem Klick auf das Bearbeiten-Icon öffnet, auf aktiviert (Setzen des Häkchens). Nach dem Übernehmen der Änderungen ist die API eingeschaltet. Dies ist dadurch zu erkennen, dass in der linken Seitenleiste ein neuer Punkt namens "API" auftaucht.
+
+#### Freischalten der REST-Routen
+
+Nachdem die API eingeschaltet wurde, sind die meisten Routen immer noch nicht freigeschaltet. Um dies zu tun, wählt man als Root-Benutzer nacheinander die Navigationspunkte Admin -> System -> API aus und wählt die Ansicht "Globale Zugriffseinstellungen" aus. Hier kann für jede Route und jede HTTP-Methode, über die die Route erreichbar ist, der Zugriff gesteuert werden. Um den Zugriff zu erlauben, muss das Häkchen in der Spalte "Zugriff" gesetzt sein. Um den Zugriff auf alle Routen (und alle HTTP-Methoden für die jeweilige Route) zu erlauben, wählt man am unteren Ende der Routen-Tabelle das unterste Häkchen aus (oberhalb des Wortes "Alle") und klickt auf Speichern. Nun sind alle REST-Routen freigeschaltet.
+
+Die REST-Routen können auch programmatisch beispielsweise in einer Migration freigeschaltet werden. Dazu gibt es seit Stud.IP 4.3 an dem `ConsumerPermissions`-Objekt die Methoden `activateRouteMap()` bzw. `deactivateRouteMap()`, welche eine `RouteMap` annimmt und alle darin enthaltenen Routen aktiviert bzw. deaktiviert.
+
+#### Einrichtung einer Anwendung für OAuth-Authentifizierung
+
+Um eine Anwendung für die API mit OAuth nutzen zu können, muss die Anwendung erst für OAuth freigegeben werden. Hiermit können Stud.IP Root-Benutzer festlegen, welche Anwendungen im Stud.IP System erlaubt sind.
+
+Zum Freischalten (oder zum Widerrufen ebendieser) navigiert man im Stud.IP System als Root-Benutzer auf den Navigationspunkt "Admin", dann "System", dann "API". Man sieht eine Liste mit registrierten Konsumenten. Konsument bezeichnet hier eine Anwendungen, welche für die API freigeschaltet ist (registriert ist).
+
+Um eine neue Anwendung für die API freizuschalten klickt man auf der Seitenleiste links auf "Neue Applikation registrieren" und füllt das sich öffnende Formular aus. Das Häkchen "Aktiviert" ganz oben im Dialog muss gesetzt sein. Der Titel der Anwendung sollte aussagekräftig sein und dem Namen der Anwendung entsprechen, um unnötige Verwirrung auf Nutzerseite zu vermeiden. Nach dem Klick auf Speichern ist die neue Anwendung freigeschaltet.
+
+In der Liste der registrierten Konsumenten taucht nun ein Eintrag für die neue Anwendung auf. Einzelne REST-Routen können für die Anwendung mit dem Klick auf das Zahnrad-Symbol des Eintrags freigeschaltet oder abgeschaltet werden.
+
+Die Anwendung verwendet dann folgende Konfigurationsdaten:
+
+* `consumer_key` - wird beim Einrichten des Konsumenten erzeugt
+* `consumer_secret`- wird beim Einrichten des Konsumenten erzeugt
+* `request_token_url` - `https://<meine-stud.ip-url>/dispatch.php/api/oauth/request_token`
+* `access_token_url` - `https://<meine-stud.ip-url>/dispatch.php/api/oauth/access_token`
+* `authorize_url` - `https://<meine-stud.ip-url>/dispatch.php/api/oauth/authorize`
+* `RESTAPI base URI` - `https://<meine-stud.ip-url>/api.php/`
+
+### Verwendung der REST-API
+
+Die REST-API kann über die HTTP-Methoden `GET`, `POST`, `PUT` und `DELETE` benutzt werden.
+Zum Lesen wird `GET` verwendet. `POST` und `PUT` sind für schreibende Zugriffe gedacht und `DELETE` zum Löschen von Daten im Stud.IP System über die API.
+
+Sollte die Notwendigkeit bestehen, einen Request mit einer anderen als der für den API-Aufruf genutzen Methode durchzuführen,
+so kann die HTTP-Methode über den Header `X-HTTP-Method-Override` ab Stud.IP 4.3 explizit gesetzt werden.
+Dies ist zum Beispiel notwendig, wenn man über einen `GET`-Request mehr Daten übermitteln will als die Länge des Requests zulässt.
+In diesem Fall kann man einen `POST`-Request absetzen und die Methode mittels des HTTP-Headers `X-HTTP-Method-Override` explizit auf `GET` setzen.
+
+### Anmeldung
+
+Die meisten REST-Routen sind nur im Zusammenhang mit einem angemeldeten Stud.IP-Nutzern sinnvoll zu benutzen.
+
+#### Anmeldung via OAuth
+
+Bei der Anmeldung via OAuth wird ein Programm vom Nutzer autorisiert, mit seinem Nutzerkonto über die API auf Daten im Stud.IP-System zuzugreifen.
+
+Damit eine Anwendung via OAuth genutzt werden kann, muss sie zuerst dafür freigeschaltet werden und über einen OAuth `consumer key`
+und OAuth `consumer secret` verfügen (siehe Abschnitt ["Einrichtung einer Anwendung für OAuth-Authentifizierung"](#einrichtung-einer-anwendung-für-oauth-authentifizierung)).
+
+Nach der Autorisierung durch den Nutzer erhält die Anwendung eigene Zugangsdaten, die dauerhaft gespeichert werden und zur Anmeldung genutzt werden können.
+
+
+#### Anmeldung mit Nutzername und Passwort
+
+Wird eine Anmeldung via Nutzername und Passwort durchgeführt, müssen für jede API-Anfrage die Zugangsdaten mitgesendet werden.
+Nutzername und Passwort eines Stud.IP Nutzers werden hierbei via HTTP Basic Authentication an die API gesendet.
+Da bei der HTTP Basic Authentication weder eine Verschlüsselung der Daten noch ein Hashing des Passwortes stattfindet,
+sollte die API über HTTPS aufgerufen werden, um einen Zugriff auf die Nutzerdaten durch Dritte zu erschweren.
+
+Die Anmeldung via Nutzername und Passwort ist zwar auf Clientseite einfacher zu realisieren, sollte aber nicht verwendet werden,
+da der Nutzername und das Passwort in die Hände von anderen Programmen gelegt werden, was ein Sicherheitsrisiko darstellen kann.
+
+
+### Abfragen
+
+REST-Routen sind unterhalb des Pfades /api.php/ der Stud.IP-Installation zu erreichen. Liegt ein Stud.IP System unter der Adresse https://studip.example.org, so wäre dessen API unter https://studip.example.org/api.php/ erreichbar. In Stud.IP-Systemen, deren URL-Umleitungen nicht definiert sind, wäre die API zum Beispiel unter https://studip.example.org/public/api.php/ erreichbar.
+
+Eine Abfrage geschieht durch das Aufrufen einer Route, zum Beispiel /user. Der komplette Pfad zur Route könnte beispielsweise so lauten: https://studip.example.org/api.php/user. Diese Anfrage kann via `GET` ausgeführt werden.
+
+#### verwendbare Parameter bei Abfragen von Listen
+
+Die folgenden Parameter sind sinnvoll, wenn Routen abgefragt werden, welche Listen von Objekten zurückliefern.
+
+| Parameter | Beschreibung |
+| ---- | ---- |
+| `offset` | gibt die Startposition innerhalb der Liste an |
+| `limit` | gibt die maximale Anzahl an Elementen an, die zurückgeliefert werden soll |
+
+### Antwortformate
+
+#### Einzelnes Objekt
+
+Die Antwortformate der API sind unterschiedlich. Wird nur ein Objekt abgefragt, so wird dieses direkt zurückgeliefert. Die Anfrage der REST-Route /user liefert beispielsweise folgende Daten zurück:
+
+```json
+ {
+ "phone" : "",
+ "datafields" : [],
+ "privadr" : "",
+ "username" : "root@studip",
+ "name" : {
+ "username" : "root@studip",
+ "formatted" : "Root Studip",
+ "suffix" : "",
+ "family" : "Studip",
+ "prefix" : "",
+ "given" : "Root"
+ },
+ "perms" : "root",
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "skype_show" : null,
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "homepage" : "",
+ "email" : "root@localhost",
+ "user_id" : "76ed43ef286fb55cf9e41beadb484a9f",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "skype" : ""
+ }
+```
+
+Es handelt sich um ein User-Objekt. Auf gleiche Art und Weise können auch zum Beispiel einzelne Veranstaltungs- oder Blubber-Objekte über die jeweiligen Routen abgefragt werden.
+
+#### Liste von Objekten
+
+Es gibt REST-Routen, die eine Liste von Objekten zurückliefern, beispielsweise alle Blubber-Objekte einer Veranstaltung. In diesem Fall sieht das Antwortformat anders aus. Es wird ein Listen-Objekt zurückgegeben, welches die folgende Struktur besitzt:
+
+```json
+{
+ "pagination" : {
+ "total" : 0,
+ "limit" : 20,
+ "offset" : 0
+ },
+ "collection" : {
+ }
+ }
+```
+
+Das zurückgelieferte Objekt besitzt zwei Unterobjekte, wovon das erste Informationen zur Paginierung liefert und das zweite die eigentlichen Objekte beinhaltet. Anhand der Paginierung kann die Anzahl der zu ladenden Objekte begrenzt und gesteuert werden.
+
+Das Unterobjekt "collection" besitzt für jedes Listen-Objekt ein eigenes Attribut. Das Attribut verweist auf die API-Route, mit der das jeweilige Objekt direkt abgefragt werden könnte.
+
+##### Beispiel zum Aufruf der API
+
+Zuerst wird die Route `/discovery` aufgerufen, um zu erfahren, welche Routen freigeschaltet sind.
+Diese Route liefert eine List mit den aktivierten Routen zurück, welche von der Anwendung genutzt werden können.
+
+Im Beispiel sollen alle Blubber-Streams des aktuell angemeldeten Nutzers ermittelt werden. Dazu wird zuerst die Route `/user` aufgerufen,
+um Informationen des aktuellen Nutzers zu finden.
+In der Antwort ist die user-ID enthalten, welche für die nächste Abfrage relevant ist: Dem Abruf aller Blubber-Streams eines Nutzers.
+Dazu wird die Route `/user/:user_id/blubber` verwendet, welche alle Blubber-Objekte eines Nutzers zurückliefert.
+
+
+#### Paginierung
+
+Mit den Request-Parametern `offset` und `limit kann die Paginierung der Daten gesteuert werden. Mittels "offset" kann die
+Startposition innerhalb einer Datensammlung festgelegt werden.
+Der Parameter `limit` gibt die maximale Anzahl an Einträgen an, die zurückgegeben werden soll.
+
+#### Statuscodes
+
+Die API liefert HTTP Statuscodes zurück, anhand derer ermittelt werden kann, ob die Anfrage erfolgreich ausgeführt wurde oder nicht.
+
+#### Fehlerverhalten
+
+Im Fehlerfall wird kein JSON zurückgeliefert. Stattdessen werden einfache, kurze Zeichenketten zurückgeliefert, welche den Fehlerfall beschreiben.
+
+Im Falle, dass die REST-Route einen `PHP Fatal Error` verursacht, wird dieser angezeigt.
+
+
+### Beispiel: Erstellung einer Anwendung für die Stud.IP API (inklusive OAuth)
+
+Auf die Stud.IP API kann je nach Programmiersprache mit relativ wenig Code zugegriffen werden.
+Hier wird die Erstellung einer kleinen Anwendung für Stud.IP beschrieben, welche sich via OAuth authentifiziert und danach Daten
+des angemeldeten Nutzers abfragt.
+
+
+#### Benötigte Module
+
+Für diese Anwendung werden die Python-Module json, requests und rauth benötigt. Python3 sollte verwendet werden.
+
+
+#### Import und Initialisierung
+
+Zuerst werden due benötigten Module geladen und ein Objekt für die OAuth1-Authentifizierung gebaut:
+
+```python
+import json
+import requests
+from rauth import OAuth1Service
+
+studip = OAuth1Service(
+ name='Stud.IP',
+ consumer_key='CONSUMER_KEY_DER_ANWENDUNG',
+ consumer_secret='CONSUMER_SECRET_DER_ANWENDUNG',
+ request_token_url='http://studip.example.org/dispatch.php/api/oauth/request_token',
+ access_token_url='http://studip.example.org/dispatch.php/api/oauth/access_token',
+ authorize_url='http://studip.example.org/dispatch.php/api/oauth/authorize',
+ base_url='http://studip.example.org/api.php/'
+)
+```
+
+
+#### Anfordern von Request-Token und Autorisierung
+
+Im Anschluss kann erst ein Request-Token angefordert werden und die URL zur Autorisierung abgerufen werden. Diese muss im Browser geöffnet werden. Sofern man nicht im Stud.IP System angemeldet ist, müssen auf der Seite der Stud.IP Installation Nutzername und Passwort eingegeben werden, um die Anwendung zu autorisieren.
+
+```python
+request_token, request_token_secret = studip.get_request_token()
+
+authorize_url = studip.get_authorize_url(request_token)
+
+print('Bitte die folgende URL aufrufen: ' + authorize_url)
+
+input('Wenn die URL aufgerufen wurde und diese Anwendung zugelassen wurde, bitte zum Fortfahren die Eingabetaste drücken!')
+```
+
+
+#### Erstellen einer authentifizierten Sitzung
+
+Nach der Autorisierung kann eine authentifizierte Sitzung gestartet werden, mit welcher Abfragen an die Stud.IP API gemacht werden können:
+
+```python
+session = studip.get_auth_session(
+ request_token,
+ request_token_secret,
+ method='`POST`',
+ data={'oauth_verifier': *}
+)
+```
+
+Da Stud.IP auch ohne oauth_verifier Abfragen der API erlaubt, kann dieser Parameter weggelassen werden.
+
+
+#### Abfragen der API
+
+Mit der Sitzung, die in obigem Abschnitt erzeugt wurde, können nun die Routen der Stud.IP API abgefragt werden. Beispielsweise kann die Route /user aufgerufen werden, um Daten zum angemeldeten Nutzer zu erhalten:
+
+```python
+user_data = session.`GET`('user')
+```
+
+
+
+### REST-API Routen
+
+Im Folgenden werden die verfügbaren REST-API Routen mitsamt den verwendbaren HTTP-Methoden dargestellt.
+
+#### Systemrouten
+
+##### `GET` /discovery
+
+Liefert eine List mit verfügbaren Routen zurück.
+
+###### Antwortformat
+
+```json
+{
+ "/studip/settings" : {
+ "`GET`" : "Grundlegende Systemeinstellungen"
+ },
+ "/user" : {
+ "`GET`" : "getUser - retrieves data of a user"
+ },
+ "/discovery" : {
+ "`GET`" : "Schnittstellenbeschreibung"
+ },
+ "/messages" : {
+ "`POST`" : "Schreibt eine neue Nachricht."
+ },
+ "/studip/news" : {
+ "`GET`" : "Globale News auslesen",
+ "`POST`" : "News anlegen"
+ }
+}
+```
+
+
+##### `GET` /studip/colors
+
+Liefert die Farbeinstellungen des Stud.IP Systems zurück. Es handelt sich um drei fest vorgegebene Farbwerte für den Hintergrund, sowie für dunkle und helle Bereiche des Stud.IP Systems.
+
+###### Antwortformat
+
+```json
+{
+ "background" : "#e1e4e9",
+ "dark" : "#34578c",
+ "light" : "#899ab9"
+}
+```
+
+
+##### `GET` /studip/news
+
+Liefert eine Liste mit globalen Ankündigungen (Ankündigungen für das gesamte Stud.IP System) zurück.
+
+###### Antwortformat
+
+```json
+{
+ "pagination" : {
+ "limit" : 20,
+ "total" : 1,
+ "offset" : 0
+ },
+ "collection" : {
+ "/api.php/news/29f2932ce32be989022c6f43b866e744" : {
+ "comments" : "/api.php/news/29f2932ce32be989022c6f43b866e744/comments",
+ "news_id" : "29f2932ce32be989022c6f43b866e744",
+ "expire" : "14562502",
+ "date" : "1468409976",
+ "chdate_uid" : "",
+ "body_html" : "<div class=\"formatted-content\">Das Stud.IP-Team heisst sie herzlich willkommen. <br>Bitte schauen Sie sich ruhig um!<br><br>Wenn Sie das System selbst installiert haben und diese News sehen, haben Sie die Demonstrationsdaten in die Datenbank eingefügt. Wenn Sie produktiv mit dem System arbeiten wollen, sollten Sie diese Daten später wieder löschen, da die Passwörter der Accounts (vor allem des root-Accounts) öffentlich bekannt sind.</div>",
+ "mkdate" : "1468409976",
+ "topic" : "Herzlich Willkommen!",
+ "body" : "Das Stud.IP-Team heisst sie herzlich willkommen. \r\nBitte schauen Sie sich ruhig um!\r\n\r\nWenn Sie das System selbst installiert haben und diese News sehen, haben Sie die Demonstrationsdaten in die Datenbank eingefügt. Wenn Sie produktiv mit dem System arbeiten wollen, sollten Sie diese Daten später wieder löschen, da die Passwörter der Accounts (vor allem des root-Accounts) öffentlich bekannt sind.",
+ "ranges" : [
+ "/api.php/user/76ed43ef286fb55cf9e41beadb484a9f/news",
+ "/api.php/studip/news"
+ ],
+ "comments_count" : 0,
+ "allow_comments" : "1",
+ "chdate" : "1468409976",
+ "user_id" : "76ed43ef286fb55cf9e41beadb484a9f"
+ }
+ }
+}
+```
+
+
+##### `POST` /studip/news
+
+Legt eine neue globale Ankündigung an.
+
+Der Titel der Ankündigung wird über den Parameter `topic` gesetzt, der Inhalt über `body`.
+Es gibt zwei optionale Parameter, `expire` und `allow_comments`. "expire" gibt die Zeitspanne in Sekunden ab dem aktuellen Datum an,
+an dem die Ankündigung ablaufen soll. Ist der Parameter `allow_comments` auf 1 gesetzt, so sind Kommentare erlaubt.
+Standardmäßig ist er auf 0 gesetzt.
+
+###### Parameter
+
+|**`POST`-Parameter** |**Format** |**Beschreibung**
+| ---- | ---- | ---- |
+| `topic` | `String` | Der Titel der Ankündigung
+| `body` | `String` | Der Inhalt der Ankündigung
+| `expire` | `Integer` | Ablaufdatum der Nachricht (in Sekunden vom aktuellen Datum gerechnet)
+| `allow_comments` | `Integer` | Gibt an, ob Kommentare erlaubt sind: 1 = erlaubt, 0 = nicht erlaubt
+
+
+
+##### `GET` /studip/settings
+
+Liefert die Werte bestimmter Konfigurationsvariablen zurück.
+
+###### Antwortformat
+```json
+{
+ "TERMIN_TYP" : {
+ "2" : {
+ "name" : "Vorbesprechung",
+ "color" : "#b02e7c",
+ "sitzung" : 0
+ },
+ "4" : {
+ "name" : "Exkursion",
+ "color" : "#f26e00",
+ "sitzung" : 0
+ },
+ "6" : {
+ "name" : "Sondersitzung",
+ "color" : "#a85d45",
+ "sitzung" : 0
+ },
+ "7" : {
+ "name" : "Vorlesung",
+ "color" : "#ca9eaf",
+ "sitzung" : 1
+ },
+ "3" : {
+ "color" : "#129c94",
+ "name" : "Klausur",
+ "sitzung" : 0
+ },
+ "1" : {
+ "sitzung" : 1,
+ "color" : "#682c8b",
+ "name" : "Sitzung"
+ },
+ "5" : {
+ "sitzung" : 0,
+ "name" : "anderer Termin",
+ "color" : "#008512"
+ }
+ },
+ "PERS_TERMIN_KAT" : {
+ "10" : {
+ "color" : "#66b570",
+ "name" : "Verabredung"
+ },
+ "5" : {
+ "color" : "#f26e00",
+ "name" : "Exkursion"
+ },
+ "12" : {
+ "color" : "#d082b0",
+ "name" : "Familie"
+ },
+ "2" : {
+ "color" : "#682c8b",
+ "name" : "Sitzung"
+ },
+ "4" : {
+ "color" : "#129c94",
+ "name" : "Klausur"
+ },
+ "1" : {
+ "name" : "Sonstiges",
+ "color" : "#008512"
+ },
+ "14" : {
+ "name" : "Reise",
+ "color" : "#f7a866"
+ },
+ "15" : {
+ "name" : "Vorlesung",
+ "color" : "#ca9eaf"
+ },
+ "8" : {
+ "color" : "#d60000",
+ "name" : "Telefonat"
+ },
+ "9" : {
+ "color" : "#ffbd33",
+ "name" : "Besprechung"
+ },
+ "11" : {
+ "color" : "#a480b9",
+ "name" : "Geburtstag"
+ },
+ "13" : {
+ "name" : "Urlaub",
+ "color" : "#70c3bf"
+ },
+ "3" : {
+ "color" : "#b02e7c",
+ "name" : "Vorbesprechung"
+ },
+ "7" : {
+ "color" : "#6ead10",
+ "name" : "Prüfung"
+ },
+ "6" : {
+ "color" : "#a85d45",
+ "name" : "Sondersitzung"
+ }
+ },
+ "SEM_TYPE" : {
+ "9" : {
+ "class" : "2",
+ "name" : "Projektgruppe"
+ },
+ "11" : {
+ "class" : "3",
+ "name" : "Kulturforum"
+ },
+ "13" : {
+ "name" : "sonstige",
+ "class" : "3"
+ },
+ "7" : {
+ "name" : "sonstige",
+ "class" : "1"
+ },
+ "3" : {
+ "class" : "1",
+ "name" : "Übung"
+ },
+ "6" : {
+ "class" : "1",
+ "name" : "Forschungsgruppe"
+ },
+ "10" : {
+ "class" : "2",
+ "name" : "sonstige"
+ },
+ "5" : {
+ "class" : "1",
+ "name" : "Colloquium"
+ },
+ "12" : {
+ "class" : "3",
+ "name" : "Veranstaltungsboard"
+ },
+ "4" : {
+ "class" : "1",
+ "name" : "Praktikum"
+ },
+ "2" : {
+ "name" : "Seminar",
+ "class" : "1"
+ },
+ "1" : {
+ "name" : "Vorlesung",
+ "class" : "1"
+ },
+ "8" : {
+ "class" : "2",
+ "name" : "Gremium"
+ },
+ "99" : {
+ "name" : "Studiengruppe",
+ "class" : "99"
+ }
+ },
+ "SUPPORT_EMAIL" : "<please insert your general contact mail-adress here>",
+ "ALLOW_CHANGE_NAME" : true,
+ "ALLOW_CHANGE_USERNAME" : true,
+ "ALLOW_CHANGE_EMAIL" : true,
+ "UNI_NAME_CLEAN" : "Stud.IP trunk",
+ "TITLES" : {
+ "accepted" : [
+ "Vorläufig akzeptierte Person",
+ "Vorläufig akzeptierte Personen"
+ ],
+ "deputy" : [
+ "Vertretung",
+ "Vertretungen"
+ ],
+ "autor" : [
+ "Studierende",
+ "Studierende"
+ ],
+ "dozent" : [
+ "Lehrende",
+ "Lehrende"
+ ],
+ "user" : [
+ "Leser/-in",
+ "Leser/-innen"
+ ],
+ "tutor" : [
+ "Tutor/-in",
+ "Tutor/-innen"
+ ]
+ },
+ "SEM_CLASS" : {
+ "2" : {
+ "bereiche" : "0",
+ "create_description" : "",
+ "description" : "Hier finden Sie virtuelle Veranstaltungen zu verschiedenen Gremien an der Universit&auml;t",
+ "name" : "Organisation",
+ "scm" : null,
+ "title_autor_plural" : null,
+ "wiki" : "CoreWiki",
+ "id" : "2",
+ "write_access_nobody" : "0",
+ "default_write_level" : "2",
+ "default_read_level" : "2",
+ "schedule" : "CoreSchedule",
+ "studygroup_mode" : "0",
+ "title_tutor_plural" : "Mitglieder",
+ "modules" : {
+ "CoreResources" : {
+ "sticky" : "0",
+ "activated" : "1"
+ },
+ "CoreAdmin" : {
+ "activated" : "1",
+ "sticky" : "1"
+ },
+ "CoreSchedule" : {
+ "sticky" : "0",
+ "activated" : "1"
+ },
+ "CoreForum" : {
+ "activated" : "1",
+ "sticky" : "0"
+ },
+ "CoreParticipants" : {
+ "sticky" : "0",
+ "activated" : "1"
+ },
+ "CoreScm" : {
+ "activated" : "0",
+ "sticky" : "1"
+ },
+ "CoreElearningInterface" : {
+ "sticky" : "1",
+ "activated" : "0"
+ },
+ "CoreStudygroupParticipants" : {
+ "activated" : "0",
+ "sticky" : "1"
+ },
+ "CoreLiterature" : {
+ "activated" : "0",
+ "sticky" : "1"
+ },
+ "CoreDocuments" : {
+ "sticky" : "0",
+ "activated" : "1"
+ },
+ "CoreCalendar" : {
+ "activated" : "0",
+ "sticky" : "1"
+ },
+ "CoreWiki" : {
+ "activated" : "1",
+ "sticky" : "0"
+ },
+ "CoreStudygroupAdmin" : {
+ "activated" : "0",
+ "sticky" : "1"
+ },
+ "CoreOverview" : {
+ "sticky" : "1",
+ "activated" : "1"
+ }
+ },
+ "title_dozent" : "LeiterIn",
+ "module" : "0",
+ "forum" : "CoreForum",
+ "documents" : "CoreDocuments",
+ "elearning_interface" : null,
+ "overview" : "CoreOverview",
+ "topic_create_autor" : "0",
+ "admission_type_default" : "0",
+ "title_dozent_plural" : "LeiterInnen",
+ "course_creation_forbidden" : "0",
+ "admin" : "CoreAdmin",
+ "title_tutor" : "Mitglied",
+ "chdate" : "1366882198",
+ "compact_mode" : "1",
+ "mkdate" : "1366882120",
+ "literature" : null,
+ "visible" : "1",
+ "resources" : "CoreResources",
+ "admission_prelim_default" : "0",
+ "calendar" : null,
+ "turnus_default" : "-1",
+ "workgroup_mode" : "1",
+ "show_browse" : "1",
+ "participants" : "CoreParticipants",
+ "show_raumzeit" : "1",
+ "title_autor" : null,
+ "only_inst_user" : "0"
+ },
+ "3" : {
+ "write_access_nobody" : "1",
+ "default_write_level" : "1",
+ "id" : "3",
+ "wiki" : "CoreWiki",
+ "title_autor_plural" : null,
+ "scm" : null,
+ "name" : "Community",
+ "description" : "Hier finden Sie virtuelle Veranstaltungen zu unterschiedlichen Themen",
+ "bereiche" : "0",
+ "create_description" : "",
+ "overview" : "CoreOverview",
+ "documents" : "CoreDocuments",
+ "elearning_interface" : null,
+ "module" : "0",
+ "title_dozent" : null,
+ "forum" : "CoreForum",
+ "title_tutor_plural" : null,
+ "modules" : {
+ "CoreAdmin" : {
+ "activated" : 1,
+ "sticky" : 1
+ },
+ "CoreOverview" : {
+ "sticky" : 1,
+ "activated" : 1
+ }
+ },
+ "studygroup_mode" : "0",
+ "schedule" : "CoreSchedule",
+ "default_read_level" : "1",
+ "admission_prelim_default" : "0",
+ "calendar" : null,
+ "literature" : "CoreLiterature",
+ "visible" : "1",
+ "resources" : "CoreResources",
+ "compact_mode" : "1",
+ "mkdate" : "1366882120",
+ "admin" : "CoreAdmin",
+ "chdate" : "1366882120",
+ "title_tutor" : null,
+ "title_dozent_plural" : null,
+ "course_creation_forbidden" : "0",
+ "topic_create_autor" : "0",
+ "admission_type_default" : "0",
+ "only_inst_user" : "0",
+ "title_autor" : null,
+ "participants" : "CoreParticipants",
+ "show_raumzeit" : "1",
+ "workgroup_mode" : "0",
+ "show_browse" : "1",
+ "turnus_default" : "-1"
+ },
+ "99" : {
+ "show_raumzeit" : "0",
+ "participants" : "CoreStudygroupParticipants",
+ "title_autor" : "Mitglied",
+ "only_inst_user" : "0",
+ "show_browse" : "0",
+ "workgroup_mode" : "0",
+ "turnus_default" : "0",
+ "visible" : "0",
+ "resources" : null,
+ "literature" : null,
+ "calendar" : null,
+ "admission_prelim_default" : "0",
+ "title_dozent_plural" : "GruppengründerInnen",
+ "course_creation_forbidden" : "1",
+ "admission_type_default" : "0",
+ "topic_create_autor" : "1",
+ "mkdate" : "1366882120",
+ "compact_mode" : "0",
+ "chdate" : "1462287763",
+ "title_tutor" : "ModeratorIn",
+ "admin" : "CoreStudygroupAdmin",
+ "elearning_interface" : null,
+ "documents" : "CoreDocuments",
+ "overview" : "CoreOverview",
+ "studygroup_mode" : "1",
+ "schedule" : "CoreSchedule",
+ "default_read_level" : "0",
+ "forum" : "CoreForum",
+ "module" : "0",
+ "title_dozent" : "GruppengründerIn",
+ "modules" : {
+ "CoreScm" : {
+ "sticky" : "0",
+ "activated" : "1"
+ },
+ "CoreElearningInterface" : {
+ "sticky" : "1",
+ "activated" : "0"
+ },
+ "CoreAdmin" : {
+ "sticky" : "1",
+ "activated" : "0"
+ },
+ "CoreSchedule" : {
+ "sticky" : "0",
+ "activated" : "1"
+ },
+ "CoreResources" : {
+ "sticky" : "1",
+ "activated" : "0"
+ },
+ "CoreForum" : {
+ "sticky" : "0",
+ "activated" : "1"
+ },
+ "CoreParticipants" : {
+ "sticky" : "1",
+ "activated" : "0"
+ },
+ "CoreWiki" : {
+ "activated" : "1",
+ "sticky" : "0"
+ },
+ "CoreStudygroupAdmin" : {
+ "activated" : "1",
+ "sticky" : "1"
+ },
+ "CoreOverview" : {
+ "activated" : "1",
+ "sticky" : "1"
+ },
+ "CoreLiterature" : {
+ "sticky" : "1",
+ "activated" : "0"
+ },
+ "CoreStudygroupParticipants" : {
+ "sticky" : "1",
+ "activated" : "1"
+ },
+ "CoreCalendar" : {
+ "activated" : "0",
+ "sticky" : "1"
+ },
+ "CoreDocuments" : {
+ "activated" : "1",
+ "sticky" : "0"
+ }
+ },
+ "title_tutor_plural" : "ModeratorInnen",
+ "wiki" : "CoreWiki",
+ "title_autor_plural" : "Mitglieder",
+ "default_write_level" : "0",
+ "write_access_nobody" : "0",
+ "id" : "99",
+ "description" : "",
+ "name" : "Studiengruppen",
+ "create_description" : "",
+ "bereiche" : "0",
+ "scm" : "CoreScm"
+ },
+ "1" : {
+ "overview" : "CoreOverview",
+ "elearning_interface" : "CoreElearningInterface",
+ "documents" : "CoreDocuments",
+ "modules" : {
+ "CoreScm" : {
+ "activated" : "1",
+ "sticky" : "0"
+ },
+ "CoreElearningInterface" : {
+ "activated" : "1",
+ "sticky" : "0"
+ },
+ "CoreResources" : {
+ "activated" : "1",
+ "sticky" : "0"
+ },
+ "CoreSchedule" : {
+ "activated" : "1",
+ "sticky" : "0"
+ },
+ "CoreAdmin" : {
+ "activated" : "1",
+ "sticky" : "1"
+ },
+ "CoreParticipants" : {
+ "activated" : "1",
+ "sticky" : "0"
+ },
+ "CoreForum" : {
+ "activated" : "1",
+ "sticky" : "0"
+ },
+ "CoreWiki" : {
+ "activated" : "1",
+ "sticky" : "0"
+ },
+ "CoreOverview" : {
+ "activated" : "1",
+ "sticky" : "1"
+ },
+ "CoreStudygroupAdmin" : {
+ "sticky" : "1",
+ "activated" : "0"
+ },
+ "CoreStudygroupParticipants" : {
+ "sticky" : "1",
+ "activated" : "0"
+ },
+ "CoreLiterature" : {
+ "activated" : "1",
+ "sticky" : "0"
+ },
+ "CoreDocuments" : {
+ "activated" : "1",
+ "sticky" : "0"
+ },
+ "CoreCalendar" : {
+ "activated" : "1",
+ "sticky" : "0"
+ }
+ },
+ "title_tutor_plural" : null,
+ "forum" : "CoreForum",
+ "module" : "0",
+ "title_dozent" : null,
+ "default_read_level" : "1",
+ "schedule" : "CoreSchedule",
+ "studygroup_mode" : "0",
+ "id" : "1",
+ "default_write_level" : "1",
+ "write_access_nobody" : "0",
+ "title_autor_plural" : null,
+ "wiki" : "CoreWiki",
+ "scm" : "CoreScm",
+ "create_description" : "",
+ "bereiche" : "1",
+ "description" : "Hier finden Sie alle in Stud.IP registrierten Lehrveranstaltungen",
+ "name" : "Lehre",
+ "title_autor" : null,
+ "only_inst_user" : "1",
+ "show_raumzeit" : "1",
+ "participants" : "CoreParticipants",
+ "turnus_default" : "0",
+ "workgroup_mode" : "0",
+ "show_browse" : "1",
+ "calendar" : "CoreCalendar",
+ "admission_prelim_default" : "0",
+ "visible" : "1",
+ "resources" : "CoreResources",
+ "literature" : "CoreLiterature",
+ "title_tutor" : null,
+ "chdate" : "1366882169",
+ "admin" : "CoreAdmin",
+ "mkdate" : "1366882120",
+ "compact_mode" : "0",
+ "admission_type_default" : "0",
+ "topic_create_autor" : "0",
+ "title_dozent_plural" : null,
+ "course_creation_forbidden" : "0"
+ }
+ },
+ "ALLOW_CHANGE_TITLE" : true,
+ "INST_TYPE" : {
+ "1" : {
+ "name" : "Einrichtung"
+ },
+ "8" : {
+ "name" : "Arbeitsgruppe"
+ },
+ "2" : {
+ "name" : "Zentrum"
+ },
+ "4" : {
+ "name" : "Abteilung"
+ },
+ "5" : {
+ "name" : "Fachbereich"
+ },
+ "6" : {
+ "name" : "Seminar"
+ },
+ "7" : {
+ "name" : "Fakultät"
+ },
+ "3" : {
+ "name" : "Lehrstuhl"
+ }
+ }
+}
+```
+
+
+
+
+#### User - Daten zu einem Nutzer
+
+##### `GET` /user
+
+Liefert Daten des aktuellen Benutzers zurück.
+
+Diese REST-Route ist vergleichbar mit dem Aufruf `User::findCurrent()` im Stud.IP System.
+Sie liefert ein Objekt mit Daten des Nutzers zurück, welcher sich über die API angemeldet hat.
+
+
+###### Antwortformat
+```json
+{
+ "name" : {
+ "family" : "Studip",
+ "given" : "Root",
+ "formatted" : "Root Studip",
+ "prefix" : "",
+ "username" : "root@studip",
+ "suffix" : ""
+ },
+ "username" : "root@studip",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "email" : "root@localhost",
+ "homepage" : "",
+ "privadr" : "",
+ "phone" : "",
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "user_id" : "76ed43ef286fb55cf9e41beadb484a9f",
+ "skype" : "",
+ "datafields" : [],
+ "skype_show" : null,
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "perms" : "root"
+}
+```
+
+##### `GET` /user/:user_id
+
+Liefert Daten eines Nutzers zurück, welcher anhand seiner Nutzer-ID referenziert wird, vergleichbar mit User::find() im Stud.IP System.
+
+###### Parameter
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| `user_id` |string, 32 Zeichen | Die Nutzer-ID des Nutzers
+
+###### Antwortformat
+
+```json
+{
+ "name" : {
+ "family" : "Studip",
+ "given" : "Root",
+ "formatted" : "Root Studip",
+ "prefix" : "",
+ "username" : "root@studip",
+ "suffix" : ""
+ },
+ "username" : "root@studip",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "email" : "root@localhost",
+ "homepage" : "",
+ "privadr" : "",
+ "phone" : "",
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "user_id" : "76ed43ef286fb55cf9e41beadb484a9f",
+ "skype" : "",
+ "datafields" : [],
+ "skype_show" : null,
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "perms" : "root"
+}
+```
+
+
+##### `DELETE` /user/:user_id
+
+Sofern der API-Nutzer Root-Berechtigungen hat und nicht vorhat, sich selbst zu löschen, kann er durch Aufruf dieser API-Route einen Nutzer löschen. Versucht der Nutzer, sich selbst zu löschen, wird der Fehlercode 400 zurückgeliefert, hat der Nutzer keine Root-Berechtigungen, wird Fehlercode 401 zurückgeliefert.
+
+###### Parameter
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| user_id |string, 32 Zeichen | Die Nutzer-ID des Nutzers
+
+
+##### `GET` /user/:user_id/blubber
+
+Diese Route liefert alle Blubber-Objekte zurück, welche im Blubber-Stream eines Nutzers erstellt wurden.
+
+###### Parameter
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| user_id |string, 32 Zeichen | Die Nutzer-ID des Nutzers
+
+
+###### Antwortformat
+
+```json
+{
+ "pagination" : {
+ "total" : 2,
+ "limit" : 20,
+ "offset" : 0
+ },
+ "collection" : {
+ "/api.php/blubber/posting/ecab929ef0dfaeca159802c018826a25" : {
+ "chdate" : "1477299897",
+ "blubber_id" : "ecab929ef0dfaeca159802c018826a25",
+ "mkdate" : "1477299897",
+ "content" : "Noch ein Test-Blubber",
+ "comments" : "/api.php/blubber/posting/ecab929ef0dfaeca159802c018826a25/comments",
+ "content_html" : "<div class=\"formatted-content\">Noch ein Test-Blubber</div>",
+ "tags" : [],
+ "root_id" : "ecab929ef0dfaeca159802c018826a25",
+ "comments_count" : "0",
+ "context_type" : "public",
+ "author" : {
+ "name" : {
+ "prefix" : "",
+ "username" : "root@studip",
+ "family" : "Studip",
+ "formatted" : "Root Studip",
+ "suffix" : "",
+ "given" : "Root"
+ },
+ "id" : "76ed43ef286fb55cf9e41beadb484a9f",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "href" : "/api.php/user/76ed43ef286fb55cf9e41beadb484a9f",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0"
+ },
+ "reshares" : []
+ },
+ "/api.php/blubber/posting/5f5f5d4f49f6122a0f2761ecf887d912" : {
+ "comments" : "/api.php/blubber/posting/5f5f5d4f49f6122a0f2761ecf887d912/comments",
+ "content_html" : "<div class=\"formatted-content\">Dies ist ein Test-Blubber</div>",
+ "content" : "Dies ist ein Test-Blubber",
+ "mkdate" : "1477299878",
+ "blubber_id" : "5f5f5d4f49f6122a0f2761ecf887d912",
+ "chdate" : "1477299878",
+ "reshares" : [],
+ "author" : {
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "href" : "/api.php/user/76ed43ef286fb55cf9e41beadb484a9f",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "id" : "76ed43ef286fb55cf9e41beadb484a9f",
+ "name" : {
+ "prefix" : "",
+ "family" : "Studip",
+ "username" : "root@studip",
+ "given" : "Root",
+ "suffix" : "",
+ "formatted" : "Root Studip"
+ },
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0"
+ },
+ "context_type" : "public",
+ "tags" : [],
+ "root_id" : "5f5f5d4f49f6122a0f2761ecf887d912",
+ "comments_count" : "0"
+ }
+ }
+}
+```
+
+
+##### `POST` /user/:user_id/blubber
+
+Erstellt ein neues Blubber-Objekt im Blubber-Stream eines Nutzers.
+
+Der Inhalt des Blubbers wird einfach als Parameter namens "content" der HTTP `POST` Anfrage gesendet. Eine spezielle Formatierung ist nicht notwendig.
+
+###### Parameter
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| user_id |String, 32 Zeichen | Die Nutzer-ID des Nutzers
+
+
+| **`POST`-Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| content | String | Der Inhalt des Blubbers
+
+
+##### `GET` /user/:user_id/contacts
+
+Es wird eine Liste mit Kontakten eines Nutzers zurückgeliefert, welche den Namen und die URLs zum Profilbild in mehreren Größen enthält. Darüber hinaus werden Nutzer-ID und API-URL zum Aufruf weiterer Daten des Nutzers mitgeteilt.
+
+###### Parameter
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| user_id |String, 32 Zeichen | Die Nutzer-ID des Nutzers
+
+###### Antwortformat
+
+```json
+{
+ "collection" : [
+ {
+ "name" : {
+ "prefix" : "",
+ "username" : "test_admin",
+ "family" : "Admin",
+ "formatted" : "Testaccount Admin",
+ "given" : "Testaccount",
+ "suffix" : ""
+ },
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "href" : "/api.php/user/6235c46eb9e962866ebdceece739ace5",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "id" : "6235c46eb9e962866ebdceece739ace5",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962"
+ },
+ {
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "id" : "e7a0a84b161f3e8c09b4a0a2e8a58147",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "href" : "/api.php/user/e7a0a84b161f3e8c09b4a0a2e8a58147",
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "name" : {
+ "formatted" : "Testaccount Autor",
+ "given" : "Testaccount",
+ "suffix" : "",
+ "prefix" : "",
+ "username" : "test_autor",
+ "family" : "Autor"
+ }
+ },
+ {
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "id" : "205f3efb7997a0fc9755da2b535038da",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "name" : {
+ "given" : "Testaccount",
+ "suffix" : "",
+ "formatted" : "Testaccount Dozent",
+ "username" : "test_dozent",
+ "prefix" : "",
+ "family" : "Dozent"
+ },
+ "href" : "/api.php/user/205f3efb7997a0fc9755da2b535038da",
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0"
+ },
+ {
+ "id" : "7e81ec247c151c02ffd479511e24cc03",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "href" : "/api.php/user/7e81ec247c151c02ffd479511e24cc03",
+ "name" : {
+ "formatted" : "Testaccount Tutor",
+ "given" : "Testaccount",
+ "suffix" : "",
+ "prefix" : "",
+ "username" : "test_tutor",
+ "family" : "Tutor"
+ }
+ }
+ ],
+ "pagination" : {
+ "offset" : 0,
+ "total" : 4,
+ "limit" : 20
+ }
+}
+
+```
+
+
+##### `GET` /user/:user_id/courses
+
+Liefert eine Liste mit Veranstaltungen eines Nutzers. Durch den Parameter "semester" können die Veranstaltungen anhand des Semesters gefiltert werden. Dieser Parameter muss die Semester-ID enthalten. Eine Liste aller Semester samt zugehöriger IDs können über die Route /semesters abgefragt werden.
+
+###### Parameter
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| user_id | String, 32 Zeichen | Die Nutzer-ID des Nutzers
+| semester | String, 32 Zeichen | Die ID eines Semesters
+
+###### Antwortformat
+
+```json
+
+{
+ "pagination" : {
+ "total" : 4,
+ "limit" : 20,
+ "offset" : 0
+ },
+ "collection" : {
+ "/api.php/course/984e34196f2e6ea6e1b2cc58f432fb8d" : {
+ "course_id" : "984e34196f2e6ea6e1b2cc58f432fb8d",
+ "type" : "1",
+ "end_semester" : "/api.php/semester/eb828ebb81bb946fac4108521a3b4697",
+ "lecturers" : {
+ "/api.php/user/205f3efb7997a0fc9755da2b535038da" : {
+ "name" : {
+ "given" : "Testaccount",
+ "formatted" : "Testaccount Dozent",
+ "suffix" : "",
+ "family" : "Dozent",
+ "username" : "test_dozent",
+ "prefix" : ""
+ },
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "href" : "/api.php/user/205f3efb7997a0fc9755da2b535038da",
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "id" : "205f3efb7997a0fc9755da2b535038da"
+ }
+ },
+ "title" : "Am Lehrstuhl s&#65533;gen 1",
+ "group" : 6,
+ "subtitle" : "",
+ "members" : {
+ "user" : "/api.php/course/984e34196f2e6ea6e1b2cc58f432fb8d/members?status=user",
+ "tutor" : "/api.php/course/984e34196f2e6ea6e1b2cc58f432fb8d/members?status=tutor",
+ "dozent_count" : 1,
+ "autor" : "/api.php/course/984e34196f2e6ea6e1b2cc58f432fb8d/members?status=autor",
+ "tutor_count" : 0,
+ "autor_count" : 0,
+ "dozent" : "/api.php/course/984e34196f2e6ea6e1b2cc58f432fb8d/members?status=dozent",
+ "user_count" : 0
+ },
+ "location" : "",
+ "modules" : {
+ "documents" : "/api.php/course/984e34196f2e6ea6e1b2cc58f432fb8d/files",
+ "wiki" : "/api.php/course/984e34196f2e6ea6e1b2cc58f432fb8d/wiki",
+ "forum" : "/api.php/course/984e34196f2e6ea6e1b2cc58f432fb8d/forum_categories"
+ },
+ "number" : "ALS1",
+ "start_semester" : "/api.php/semester/eb828ebb81bb946fac4108521a3b4697",
+ "description" : ""
+ },
+ "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde" : {
+ "start_semester" : "/api.php/semester/f2b4fdf5ac59a9cb57dd73c4d3bbb651",
+ "description" : "",
+ "number" : "12345",
+ "modules" : {
+ "documents" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/files",
+ "wiki" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/wiki",
+ "forum" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/forum_categories"
+ },
+ "location" : "",
+ "members" : {
+ "user_count" : 0,
+ "dozent" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/members?status=dozent",
+ "tutor_count" : 1,
+ "autor" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/members?status=autor",
+ "autor_count" : 1,
+ "tutor" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/members?status=tutor",
+ "dozent_count" : 1,
+ "user" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/members?status=user"
+ },
+ "subtitle" : "eine normale Lehrveranstaltung",
+ "group" : 5,
+ "title" : "Test Lehrveranstaltung",
+ "lecturers" : {
+ "/api.php/user/205f3efb7997a0fc9755da2b535038da" : {
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "id" : "205f3efb7997a0fc9755da2b535038da",
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "href" : "/api.php/user/205f3efb7997a0fc9755da2b535038da",
+ "name" : {
+ "suffix" : "",
+ "username" : "test_dozent",
+ "family" : "Dozent",
+ "prefix" : "",
+ "given" : "Testaccount",
+ "formatted" : "Testaccount Dozent"
+ },
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962"
+ }
+ },
+ "end_semester" : "/api.php/semester/f2b4fdf5ac59a9cb57dd73c4d3bbb651",
+ "type" : "1",
+ "course_id" : "a07535cf2f8a72df33c12ddfa4b53dde"
+ },
+ "/api.php/course/29d755d51d2bf920ef2017db3359bdb2" : {
+ "type" : "1",
+ "course_id" : "29d755d51d2bf920ef2017db3359bdb2",
+ "title" : "Fakultäten faktorisieren, fakultative Fakultätsvorlesung Fünf",
+ "end_semester" : "/api.php/semester/eb828ebb81bb946fac4108521a3b4697",
+ "lecturers" : {
+ "/api.php/user/205f3efb7997a0fc9755da2b535038da" : {
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "name" : {
+ "given" : "Testaccount",
+ "formatted" : "Testaccount Dozent",
+ "suffix" : "",
+ "family" : "Dozent",
+ "username" : "test_dozent",
+ "prefix" : ""
+ },
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "id" : "205f3efb7997a0fc9755da2b535038da",
+ "href" : "/api.php/user/205f3efb7997a0fc9755da2b535038da"
+ }
+ },
+ "location" : "",
+ "members" : {
+ "user" : "/api.php/course/29d755d51d2bf920ef2017db3359bdb2/members?status=user",
+ "dozent_count" : 1,
+ "tutor" : "/api.php/course/29d755d51d2bf920ef2017db3359bdb2/members?status=tutor",
+ "autor_count" : 0,
+ "tutor_count" : 0,
+ "autor" : "/api.php/course/29d755d51d2bf920ef2017db3359bdb2/members?status=autor",
+ "dozent" : "/api.php/course/29d755d51d2bf920ef2017db3359bdb2/members?status=dozent",
+ "user_count" : 0
+ },
+ "subtitle" : "",
+ "group" : 6,
+ "start_semester" : "/api.php/semester/eb828ebb81bb946fac4108521a3b4697",
+ "description" : "",
+ "number" : "4F5",
+ "modules" : {
+ "wiki" : "/api.php/course/29d755d51d2bf920ef2017db3359bdb2/wiki",
+ "forum" : "/api.php/course/29d755d51d2bf920ef2017db3359bdb2/forum_categories",
+ "documents" : "/api.php/course/29d755d51d2bf920ef2017db3359bdb2/files"
+ }
+ },
+ "/api.php/course/9bbd57993e9cf6e1ed82ab5273af09ad" : {
+ "lecturers" : {
+ "/api.php/user/205f3efb7997a0fc9755da2b535038da" : {
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "id" : "205f3efb7997a0fc9755da2b535038da",
+ "href" : "/api.php/user/205f3efb7997a0fc9755da2b535038da",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "name" : {
+ "suffix" : "",
+ "family" : "Dozent",
+ "username" : "test_dozent",
+ "prefix" : "",
+ "formatted" : "Testaccount Dozent",
+ "given" : "Testaccount"
+ }
+ }
+ },
+ "end_semester" : "/api.php/semester/eb828ebb81bb946fac4108521a3b4697",
+ "title" : "Externe Einrichtungen extern einrichten, Einf&#65533;hrungsveranstaltung Eins",
+ "course_id" : "9bbd57993e9cf6e1ed82ab5273af09ad",
+ "type" : "1",
+ "modules" : {
+ "documents" : "/api.php/course/9bbd57993e9cf6e1ed82ab5273af09ad/files",
+ "wiki" : "/api.php/course/9bbd57993e9cf6e1ed82ab5273af09ad/wiki",
+ "forum" : "/api.php/course/9bbd57993e9cf6e1ed82ab5273af09ad/forum_categories"
+ },
+ "number" : "5E-1",
+ "start_semester" : "/api.php/semester/eb828ebb81bb946fac4108521a3b4697",
+ "description" : "",
+ "group" : 6,
+ "subtitle" : "",
+ "members" : {
+ "dozent" : "/api.php/course/9bbd57993e9cf6e1ed82ab5273af09ad/members?status=dozent",
+ "user_count" : 0,
+ "autor_count" : 0,
+ "tutor_count" : 0,
+ "autor" : "/api.php/course/9bbd57993e9cf6e1ed82ab5273af09ad/members?status=autor",
+ "dozent_count" : 1,
+ "tutor" : "/api.php/course/9bbd57993e9cf6e1ed82ab5273af09ad/members?status=tutor",
+ "user" : "/api.php/course/9bbd57993e9cf6e1ed82ab5273af09ad/members?status=user"
+ },
+ "location" : ""
+ }
+ }
+}
+
+```
+
+
+##### `GET` /user/:user_id/events
+
+Liest die Liste der Kalendereinträge eines Nutzers der nächsten 2 Wochen aus.
+Wird statt dieser Route die Route /user/:user_id/events.ics aufgerufen, erhält man die Kalendereinträge im iCal-Format.
+
+###### Parameter
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| user_id |String, 32 Zeichen | Die Nutzer-ID des Nutzers
+
+
+
+##### `GET` /user/:user_id/institutes
+
+Liefert eine Liste der Institute eines Nutzers zurück. Das zurückgelieferte "collection"-Objekt enthält hier nicht die Routen zu den einzelnen Objekten, sondern zwei Attribute mit Namen "work" und "study", welche wiederrum ein Array mit Institut-Objekten enthalten. Somit werden Institute sortiert nach ihrer Funktion für den gewählten Nutzer zurückgeliefert.
+
+###### Parameter
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| user_id |String, 32 Zeichen | Die Nutzer-ID des Nutzers
+
+###### Antwortformat
+
+```json
+
+{
+ "collection" : {
+ "study" : [],
+ "work" : [
+ {
+ "fax" : "",
+ "street" : "Geismar Landstr. 17b",
+ "room" : "",
+ "faculty_street" : "Geismar Landstr. 17b",
+ "faculty_city" : "37083 Göttingen",
+ "city" : "37083 Göttingen",
+ "consultation" : "",
+ "faculty_name" : "Test Fakultät",
+ "perms" : "dozent",
+ "institute_id" : "1535795b0d6ddecac6813f5f6ac47ef2",
+ "name" : "Test Fakultät",
+ "phone" : ""
+ },
+ {
+ "room" : "",
+ "faculty_street" : "Geismar Landstr. 17b",
+ "faculty_city" : "37083 Göttingen",
+ "fax" : "",
+ "street" : "",
+ "phone" : "",
+ "consultation" : "",
+ "city" : "",
+ "institute_id" : "2560f7c7674942a7dce8eeb238e15d93",
+ "faculty_name" : "Test Fakultät",
+ "perms" : "dozent",
+ "name" : "Test Einrichtung"
+ },
+ {
+ "name" : "Test Lehrstuhl",
+ "faculty_name" : "Test Fakultät",
+ "institute_id" : "536249daa596905f433e1f73578019db",
+ "perms" : "dozent",
+ "city" : "",
+ "consultation" : "",
+ "phone" : "",
+ "street" : "",
+ "fax" : "",
+ "faculty_city" : "37083 Göttingen",
+ "faculty_street" : "Geismar Landstr. 17b",
+ "room" : ""
+ },
+ {
+ "faculty_street" : "",
+ "faculty_city" : "",
+ "room" : "",
+ "fax" : "",
+ "street" : "",
+ "phone" : "",
+ "faculty_name" : "externe Bildungseinrichtungen",
+ "institute_id" : "7a4f19a0a2c321ab2b8f7b798881af7c",
+ "perms" : "dozent",
+ "name" : "externe Einrichtung A",
+ "consultation" : "",
+ "city" : ""
+ }
+ ]
+ },
+ "pagination" : {
+ "limit" : 20,
+ "total" : 2,
+ "offset" : 0
+ }
+}
+
+```
+
+
+##### `GET` /user/:user_id/news
+
+Ankündigungen eines Nutzers auslesen.
+
+###### Parameter
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| user_id |String, 32 Zeichen | Die Nutzer-ID des Nutzers
+
+
+###### Antwortformat
+
+```json
+
+{
+ "collection" : {
+ "/api.php/news/29f2932ce32be989022c6f43b866e744" : {
+ "chdate_uid" : "",
+ "news_id" : "29f2932ce32be989022c6f43b866e744",
+ "chdate" : "1476445862",
+ "mkdate" : "1476445862",
+ "date" : "1476445862",
+ "expire" : "14562502",
+ "comments_count" : 1,
+ "body_html" : "<div class=\"formatted-content\">Das Stud.IP-Team heisst sie herzlich willkommen. <br>Bitte schauen Sie sich ruhig um!<br><br>Wenn Sie das System selbst installiert haben und diese News sehen, haben Sie die Demonstrationsdaten in die Datenbank eingefügt. Wenn Sie produktiv mit dem System arbeiten wollen, sollten Sie diese Daten später wieder löschen, da die Passwörter der Accounts (vor allem des root-Accounts) öffentlich bekannt sind.</div>",
+ "allow_comments" : "1",
+ "topic" : "Herzlich Willkommen!",
+ "user_id" : "76ed43ef286fb55cf9e41beadb484a9f",
+ "comments" : "/api.php/news/29f2932ce32be989022c6f43b866e744/comments",
+ "ranges" : [
+ "/api.php/user/76ed43ef286fb55cf9e41beadb484a9f/news",
+ "/api.php/studip/news"
+ ],
+ "body" : "Das Stud.IP-Team heisst sie herzlich willkommen. \r\nBitte schauen Sie sich ruhig um!\r\n\r\nWenn Sie das System selbst installiert haben und diese News sehen, haben Sie die Demonstrationsdaten in die Datenbank eingefügt. Wenn Sie produktiv mit dem System arbeiten wollen, sollten Sie diese Daten später wieder löschen, da die Passwörter der Accounts (vor allem des root-Accounts) öffentlich bekannt sind."
+ }
+ },
+ "pagination" : {
+ "offset" : 0,
+ "total" : 1,
+ "limit" : 20
+ }
+}
+
+```
+
+
+##### `POST` /user/:user_id/news
+
+Erstellt eine neue Ankündigung des Nutzers. Diese Route verhält sich wie die `POST`-Route /studip/news.
+
+###### Parameter
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| user_id | String, 32 Zeichen | Die Nutzer-ID des Nutzers
+
+
+| **`POST`-Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| topic | String | Der Titel der Ankündigung
+| body | String | Der Inhalt der Ankündigung
+| expire | Integer | Ablaufdatum der Nachricht (in Sekunden vom aktuellen Datum gerechnet)
+| allow_comments | Integer | Gibt an, ob Kommentare erlaubt sind: 1 = erlaubt, 0 = nicht erlaubt
+
+
+##### `GET` /user/:user_id/schedule und `GET` /user/:user_id/schedule/:semester_id
+
+Liest den wöchentlichen Stundenplan des Nutzers vom aktuellen Semesters aus. Soll hingegen der Zeitplan eines bestimmten Semesters ausgelesen werden, so wird die Route /user/:user_id/schedule/:semester_id verwendet.
+
+Diese Route liefert keine "collection"-Liste zurück, sondern ein JSON-Objekt, welches die Attribute 1-7 besitzt. Diese verweisen jeweils auf ein Array, das die Stundenplan-Einträge beinhaltet.
+
+###### Parameter
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| user_id | String, 32 Zeichen | Die Nutzer-ID des Nutzers
+
+
+#### Course - Daten zu einer Veranstaltung
+
+##### `GET` /course/:course_id
+
+Es wird ein Veranstaltungs-Objekt zurückgeliefert, welches alle Grunddaten einer Veranstaltung beinhaltet. Vergleichbar mit dem Aufruf von Course::find() im Stud.IP System.
+
+###### Parameter
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| course_id | String, 32 Zeichen | Die ID der Veranstaltung
+
+###### Antwortformat
+
+```json
+
+{
+ "start_semester" : "/api.php/semester/f2b4fdf5ac59a9cb57dd73c4d3bbb651",
+ "number" : "12345",
+ "lecturers" : {
+ "/api.php/user/205f3efb7997a0fc9755da2b535038da" : {
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "name" : {
+ "formatted" : "Testaccount Dozent",
+ "username" : "test_dozent",
+ "given" : "Testaccount",
+ "family" : "Dozent",
+ "prefix" : "",
+ "suffix" : ""
+ },
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "id" : "205f3efb7997a0fc9755da2b535038da",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "href" : "/api.php/user/205f3efb7997a0fc9755da2b535038da"
+ }
+ },
+ "members" : {
+ "tutor" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/members?status=tutor",
+ "dozent_count" : 1,
+ "tutor_count" : 1,
+ "dozent" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/members?status=dozent",
+ "user" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/members?status=user",
+ "autor_count" : 1,
+ "user_count" : 0,
+ "autor" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/members?status=autor"
+ },
+ "modules" : {
+ "forum" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/forum_categories",
+ "documents" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/files",
+ "wiki" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/wiki"
+ },
+ "type" : "1",
+ "location" : "",
+ "title" : "Test Lehrveranstaltung",
+ "end_semester" : "/api.php/semester/f2b4fdf5ac59a9cb57dd73c4d3bbb651",
+ "description" : "",
+ "subtitle" : "eine normale Lehrveranstaltung",
+ "course_id" : "a07535cf2f8a72df33c12ddfa4b53dde"
+}
+
+```
+
+
+##### `GET` /course/:course_id/blubber
+
+Es wird eine Liste mit Blubber-Objekten zurückgeliefert.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| course_id | String, 32 Zeichen | Die ID der Veranstaltung
+
+###### Antwortformat
+
+```json
+{
+ "pagination" : {
+ "limit" : 20,
+ "offset" : 0,
+ "total" : 1
+ },
+ "collection" : {
+ "/api.php/blubber/posting/6b7ff409eeaa73cb8927ef27ac623f6d" : {
+ "comments_count" : "1",
+ "reshares" : [],
+ "author" : {
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "id" : "205f3efb7997a0fc9755da2b535038da",
+ "href" : "/api.php/user/205f3efb7997a0fc9755da2b535038da",
+ "name" : {
+ "family" : "Dozent",
+ "given" : "Testaccount",
+ "formatted" : "Testaccount Dozent",
+ "username" : "test_dozent",
+ "prefix" : "",
+ "suffix" : ""
+ },
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962"
+ },
+ "blubber_id" : "6b7ff409eeaa73cb8927ef27ac623f6d",
+ "tags" : [],
+ "chdate" : "1478853090",
+ "root_id" : "6b7ff409eeaa73cb8927ef27ac623f6d",
+ "mkdate" : "1478853090",
+ "comments" : "/api.php/blubber/posting/6b7ff409eeaa73cb8927ef27ac623f6d/comments",
+ "content_html" : "<div class=\"formatted-content\">In dieser Veranstaltung gibt es einen Test-Blubber</div>",
+ "context_type" : "course",
+ "course_id" : "a07535cf2f8a72df33c12ddfa4b53dde",
+ "content" : "In dieser Veranstaltung gibt es einen Test-Blubber"
+ }
+ }
+}
+
+```
+
+
+##### `POST` /course/:course_id/blubber
+
+Der Veranstaltung wird ein neuer Blubber hinzugefügt. Diese Route verhält sich genauso wie die Route /user/:user_id/blubber.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| course_id | String, 32 Zeichen | Die ID der Veranstaltung
+
+
+| **`POST`-Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| content | String | Der Inhalt des Blubbers (`POST`-Parameter)
+
+
+##### `GET` /course/:course_id/events
+
+Liefert eine Liste mit Kalendereinträge einer Veranstaltung zurück.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| course_id | String, 32 Zeichen | Die ID der Veranstaltung
+
+###### Antwortformat
+
+```json
+
+{
+ "collection" : [
+ {
+ "event_id" : "b99e85888891b41e1e814b7934a6791a",
+ "start" : "1460358000",
+ "title" : "Mo , 11.04.2016 09:00 - 12:00",
+ "description" : "",
+ "categories" : "Sitzung",
+ "end" : "1460368800",
+ "room" : "Raum: Hörsaal 1",
+ "canceled" : false,
+ "deleted" : null
+ },
+ {
+ "deleted" : null,
+ "canceled" : false,
+ "categories" : "Sitzung",
+ "end" : "1461236400",
+ "room" : "Raum: Seminarraum 1",
+ "description" : "",
+ "title" : "Do , 21.04.2016 09:00 - 13:00",
+ "event_id" : "86abb6bfe25cc2ac254b91474c95549f",
+ "start" : "1461222000"
+ },
+ {
+ "deleted" : null,
+ "canceled" : false,
+ "room" : "Raum: Hörsaal 1",
+ "end" : "1461578400",
+ "categories" : "Sitzung",
+ "description" : "",
+ "title" : "Mo , 25.04.2016 09:00 - 12:00",
+ "start" : "1461567600",
+ "event_id" : "61eb9961107feca471b5db61bce1a76c"
+ },
+ {
+ "description" : "",
+ "title" : "Do , 05.05.2016 09:00 - 13:00",
+ "event_id" : "d08073764f751b3c54db4337a7113e77",
+ "start" : "1462431600",
+ "deleted" : true,
+ "canceled" : "Christi Himmelfahrt",
+ "categories" : "Sitzung",
+ "room" : "(Christi Himmelfahrt)",
+ "end" : "1462446000"
+ },
+ {
+ "description" : "",
+ "title" : "Mo , 09.05.2016 09:00 - 12:00",
+ "event_id" : "6061c46538054821deb1031d70997ee9",
+ "start" : "1462777200",
+ "deleted" : null,
+ "canceled" : false,
+ "categories" : "Sitzung",
+ "end" : "1462788000",
+ "room" : "Raum: Hörsaal 1"
+ },
+ {
+ "title" : "Do , 19.05.2016 09:00 - 13:00",
+ "event_id" : "b6b3767d913faad2bbb44135a6321295",
+ "start" : "1463641200",
+ "description" : "",
+ "canceled" : false,
+ "categories" : "Sitzung",
+ "room" : "Raum: Seminarraum 1",
+ "end" : "1463655600",
+ "deleted" : null
+ },
+ {
+ "event_id" : "d0d018a4aed955ecf85e01e07d81147a",
+ "start" : "1463986800",
+ "title" : "Mo , 23.05.2016 09:00 - 12:00",
+ "description" : "",
+ "categories" : "Sitzung",
+ "room" : "Raum: Hörsaal 1",
+ "end" : "1463997600",
+ "canceled" : false,
+ "deleted" : null
+ },
+ {
+ "deleted" : null,
+ "canceled" : false,
+ "categories" : "Sitzung",
+ "room" : "Raum: Seminarraum 1",
+ "end" : "1464865200",
+ "description" : "",
+ "title" : "Do , 02.06.2016 09:00 - 13:00",
+ "event_id" : "12a11ff3f258ab706e77f85d7f1f28f9",
+ "start" : "1464850800"
+ },
+ {
+ "deleted" : null,
+ "canceled" : false,
+ "categories" : "Sitzung",
+ "end" : "1465207200",
+ "room" : "Raum: Hörsaal 1",
+ "description" : "",
+ "title" : "Mo , 06.06.2016 09:00 - 12:00",
+ "event_id" : "124609f704bff3379705bb8a8fce3ae1",
+ "start" : "1465196400"
+ },
+ {
+ "categories" : "Sitzung",
+ "room" : "Raum: Seminarraum 1",
+ "end" : "1466074800",
+ "canceled" : false,
+ "deleted" : null,
+ "event_id" : "4be63f71cf1d08b87b7d731c3a28a572",
+ "start" : "1466060400",
+ "title" : "Do , 16.06.2016 09:00 - 13:00",
+ "description" : ""
+ },
+ {
+ "description" : "",
+ "event_id" : "98a296243626ea32243fb3507af38bce",
+ "start" : "1466406000",
+ "title" : "Mo , 20.06.2016 09:00 - 12:00",
+ "deleted" : null,
+ "categories" : "Sitzung",
+ "room" : "Raum: Hörsaal 1",
+ "end" : "1466416800",
+ "canceled" : false
+ },
+ {
+ "title" : "Do , 30.06.2016 09:00 - 13:00",
+ "start" : "1467270000",
+ "event_id" : "cedcf924d509a3c1acc8fcde47ab198b",
+ "description" : "",
+ "canceled" : false,
+ "end" : "1467284400",
+ "room" : "Raum: Seminarraum 1",
+ "categories" : "Sitzung",
+ "deleted" : null
+ },
+ {
+ "deleted" : null,
+ "canceled" : false,
+ "categories" : "Sitzung",
+ "room" : "Raum: Hörsaal 1",
+ "end" : "1467626400",
+ "description" : "",
+ "title" : "Mo , 04.07.2016 09:00 - 12:00",
+ "event_id" : "341e8520fe69a90fb437cb9e0ba3368c",
+ "start" : "1467615600"
+ },
+ {
+ "title" : "Do , 14.07.2016 09:00 - 13:00",
+ "event_id" : "bab7532584170e5aaf44eb217b1d19e0",
+ "start" : "1468479600",
+ "description" : "",
+ "canceled" : false,
+ "categories" : "Sitzung",
+ "room" : "keine Raumangabe",
+ "end" : "1468494000",
+ "deleted" : null
+ }
+ ],
+ "pagination" : {
+ "limit" : 20,
+ "offset" : 0,
+ "total" : 14
+ }
+}
+
+```
+
+
+##### `GET` /course/:course_id/files
+
+Liefert eine Liste mit Dateien und Ordnern einer Veranstaltung zurück.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| course_id | String, 32 Zeichen | Die ID der Veranstaltung
+
+###### Antwortformat
+
+```json
+
+{
+ "collection" : {
+ "/api.php/file/ad8dc6a6162fb0fe022af4a62a15e309" : {
+ "course" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde",
+ "permissions" : [
+ "visible",
+ "writable",
+ "readable"
+ ],
+ "chdate" : 1343924877,
+ "mkdate" : 1343924873,
+ "author" : "/api.php/user/76ed43ef286fb55cf9e41beadb484a9f",
+ "name" : "Hausaufgaben",
+ "folder_id" : "ad8dc6a6162fb0fe022af4a62a15e309",
+ "range_id" : "373a72966cf45c484b4b0b07dba69a64",
+ "documents" : [],
+ "description" : ""
+ },
+ "/api.php/file/ca002fbae136b07e4df29e0136e3bd32" : {
+ "chdate" : 1343924894,
+ "mkdate" : 1343924407,
+ "course" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde",
+ "permissions" : [
+ "visible",
+ "readable",
+ "extendable"
+ ],
+ "name" : "Allgemeiner Dateiordner",
+ "author" : "/api.php/user/76ed43ef286fb55cf9e41beadb484a9f",
+ "folder_id" : "ca002fbae136b07e4df29e0136e3bd32",
+ "description" : "Ablage für allgemeine Ordner und Dokumente der Veranstaltung",
+ "documents" : {
+ "/api.php/file/6b606bd3d6d6cda829200385fa79fcbf" : {
+ "filesize" : 314146,
+ "course" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde",
+ "filename" : "mappe_studip-el.pdf",
+ "chdate" : 1343924841,
+ "mkdate" : 1343924827,
+ "file_id" : "6b606bd3d6d6cda829200385fa79fcbf",
+ "author" : "/api.php/user/76ed43ef286fb55cf9e41beadb484a9f",
+ "name" : "Stud.IP-Produktbrosch?re im PDF-Format",
+ "downloads" : 0,
+ "protected" : false,
+ "range_id" : "ca002fbae136b07e4df29e0136e3bd32",
+ "content" : "/api.php/file/6b606bd3d6d6cda829200385fa79fcbf/content",
+ "description" : ""
+ }
+ },
+ "range_id" : "a07535cf2f8a72df33c12ddfa4b53dde"
+ },
+ "/api.php/file/df122112a21812ff4ffcf1965cb48fc3" : {
+ "range_id" : "2f597139a049a768dbf8345a0a0af3de",
+ "documents" : [],
+ "description" : "Ablage für Ordner und Dokumente dieser Gruppe",
+ "folder_id" : "df122112a21812ff4ffcf1965cb48fc3",
+ "name" : "Dateiordner der Gruppe: Studierende",
+ "author" : "/api.php/user/76ed43ef286fb55cf9e41beadb484a9f",
+ "chdate" : 1343924860,
+ "mkdate" : 1343924860,
+ "permissions" : [
+ "visible",
+ "writable",
+ "readable",
+ "extendable"
+ ],
+ "course" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde"
+ }
+ },
+ "pagination" : {
+ "limit" : 20,
+ "total" : 3,
+ "offset" : 0
+ }
+}
+
+```
+
+
+##### `GET` /course/:course_id/forum_categories
+
+Liefert die Liste mit Forenbereichen einer Veranstaltung zurück. Um an die einzelnen Forenbeiträge zu gelangen, muss die Route /forum_category/:category_id/areas aufgerufen werden.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| course_id | String, 32 Zeichen | Die ID der Veranstaltung
+
+###### Antwortformat
+
+```json
+
+{
+ "pagination" : {
+ "offset" : 0,
+ "limit" : 20,
+ "total" : 1
+ },
+ "collection" : {
+ "/api.php/forum_category/a07535cf2f8a72df33c12ddfa4b53dde" : {
+ "entry_name" : "Allgemein",
+ "pos" : "0",
+ "areas_count" : 1,
+ "seminar_id" : "a07535cf2f8a72df33c12ddfa4b53dde",
+ "areas" : "/api.php/forum_category/a07535cf2f8a72df33c12ddfa4b53dde/areas",
+ "category_id" : "a07535cf2f8a72df33c12ddfa4b53dde",
+ "course" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde"
+ }
+ }
+}
+
+```
+
+
+##### `POST` /course/:course_id/forum_categories
+
+Es wird ein neuer Forenbereich erstellt. Die `POST`-Anfrage benötigt den Parameter "name", welcher den Namen der neuen Kategorie angibt.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| course_id | String, 32 Zeichen | Die ID der Veranstaltung
+
+
+| **`POST`-Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| name | String | Der Name der neuen Foren-Kategorie (`POST`-Parameter)
+
+
+##### `GET` /course/:course_id/members
+
+Liefert die Teilnehmer einer Veranstaltung zurück. Über den Parameter "status" können die Teilnehmer anhand ihres Status gefiltert werden. Der Parameter "status" darf nur aus einem der folgenden Wörter bestehen: "user", "autor", "tutor", "dozent".
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| course_id | String, 32 Zeichen | Die ID der Veranstaltung
+
+###### Antwortformat
+
+```json
+
+{
+ "pagination" : {
+ "offset" : 0,
+ "limit" : 20,
+ "total" : 3
+ },
+ "collection" : {
+ "/api.php/user/205f3efb7997a0fc9755da2b535038da" : {
+ "member" : {
+ "href" : "/api.php/user/205f3efb7997a0fc9755da2b535038da",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "id" : "205f3efb7997a0fc9755da2b535038da",
+ "name" : {
+ "given" : "Testaccount",
+ "family" : "Dozent",
+ "suffix" : "",
+ "username" : "test_dozent",
+ "prefix" : "",
+ "formatted" : "Testaccount Dozent"
+ },
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0"
+ },
+ "status" : "dozent"
+ },
+ "/api.php/user/e7a0a84b161f3e8c09b4a0a2e8a58147" : {
+ "status" : "autor",
+ "member" : {
+ "href" : "/api.php/user/e7a0a84b161f3e8c09b4a0a2e8a58147",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "id" : "e7a0a84b161f3e8c09b4a0a2e8a58147",
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "name" : {
+ "given" : "Testaccount",
+ "suffix" : "",
+ "family" : "Autor",
+ "username" : "test_autor",
+ "prefix" : "",
+ "formatted" : "Testaccount Autor"
+ },
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0"
+ }
+ },
+ "/api.php/user/7e81ec247c151c02ffd479511e24cc03" : {
+ "member" : {
+ "name" : {
+ "given" : "Testaccount",
+ "username" : "test_tutor",
+ "suffix" : "",
+ "family" : "Tutor",
+ "prefix" : "",
+ "formatted" : "Testaccount Tutor"
+ },
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "id" : "7e81ec247c151c02ffd479511e24cc03",
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "href" : "/api.php/user/7e81ec247c151c02ffd479511e24cc03",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962"
+ },
+ "status" : "tutor"
+ }
+ }
+}
+
+```
+
+
+##### `GET` /course/:course_id/news
+
+Liefert die Ankündigungen einer Veranstaltung zurück.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| course_id | String, 32 Zeichen | Die ID der Veranstaltung
+
+###### Antwortformat
+
+```json
+
+{
+ "collection" : {
+ "/api.php/news/d8d92fb84dbb1150f09199f923c54aa8" : {
+ "expire" : "691140",
+ "chdate_uid" : "",
+ "topic" : "Ausfall der Veranstaltung",
+ "ranges" : [
+ "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/news"
+ ],
+ "user_id" : "205f3efb7997a0fc9755da2b535038da",
+ "date" : "1478818800",
+ "chdate" : "1478853886",
+ "mkdate" : "1478853886",
+ "news_id" : "d8d92fb84dbb1150f09199f923c54aa8",
+ "body" : "Die Veranstaltung f&#65533;llt am 11.11. wegen Beginn der \"5. Jahreszeit\" aus!",
+ "body_html" : "<div class=\"formatted-content\">Die Veranstaltung f&#65533;llt am 11.11. wegen Beginn der &quot;5. Jahreszeit&quot; aus!</div>",
+ "allow_comments" : "0"
+ }
+ },
+ "pagination" : {
+ "limit" : 20,
+ "offset" : 0,
+ "total" : 1
+ }
+}
+
+```
+
+
+##### `POST` /course/:course_id/news
+
+Legt eine neue Ankündigung in der Veranstaltung an. Diese Route verhält sich wie die `POST`-Route /studip/news.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| course_id | String, 32 Zeichen | Die ID der Veranstaltung
+
+
+| **`POST`-Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| topic | String | Der Titel der Ankündigung
+| body | String | Der Inhalt der Ankündigung
+| expire | Integer | Ablaufdatum der Nachricht (in Sekunden vom aktuellen Datum gerechnet)
+| allow_comments | Integer | Gibt an, ob Kommentare erlaubt sind: 1 = erlaubt, 0 = nicht erlaubt
+
+
+##### `GET` /course/:course_id/wiki
+
+Liefert die Liste mit allen Seiten des Wikis einer Veranstaltung zurück, wobei von jeder Seite die aktuellste Version zurückgeliefert wird. Sind keine Seiten vorhanden, besteht die Liste mit Seiten nur aus der Hauptseite.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| course_id | String, 32 Zeichen | Die ID der Veranstaltung
+
+###### Antwortformat
+
+```json
+
+{
+ "pagination" : {
+ "limit" : 20,
+ "total" : 3,
+ "offset" : 0
+ },
+ "collection" : {
+ "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/wiki/Weitere Seite" : {
+ "version" : "1",
+ "keyword" : "Weitere Seite",
+ "range_id" : "a07535cf2f8a72df33c12ddfa4b53dde",
+ "chdate" : "1478854048",
+ "user" : {
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "name" : {
+ "family" : "Dozent",
+ "prefix" : "",
+ "formatted" : "Testaccount Dozent",
+ "username" : "test_dozent",
+ "suffix" : "",
+ "given" : "Testaccount"
+ },
+ "id" : "205f3efb7997a0fc9755da2b535038da",
+ "href" : "/api.php/user/205f3efb7997a0fc9755da2b535038da"
+ }
+ },
+ "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/wiki/WikiWikiWeb" : {
+ "keyword" : "WikiWikiWeb",
+ "version" : "1",
+ "user" : {
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "id" : "205f3efb7997a0fc9755da2b535038da",
+ "href" : "/api.php/user/205f3efb7997a0fc9755da2b535038da",
+ "name" : {
+ "family" : "Dozent",
+ "username" : "test_dozent",
+ "formatted" : "Testaccount Dozent",
+ "prefix" : "",
+ "suffix" : "",
+ "given" : "Testaccount"
+ }
+ },
+ "chdate" : "1478854031",
+ "range_id" : "a07535cf2f8a72df33c12ddfa4b53dde"
+ },
+ "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde/wiki/WikiWikWeb" : {
+ "keyword" : "WikiWikWeb",
+ "version" : 0,
+ "chdate" : null,
+ "range_id" : "a07535cf2f8a72df33c12ddfa4b53dde"
+ }
+ }
+}
+
+```
+
+
+##### `GET` /course/:course_id/wiki/:seitenname
+
+Liefert eine Wikiseite mit dem angegebenen Seitennamen zurück.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| course_id | String, 32 Zeichen | Die ID der Veranstaltung
+| seitenname | String | Der Name der Wiki-Seite
+
+###### Antwortformat
+
+```json
+
+{
+ "content_html" : "<div class=\"formatted-content\">Dies ist eine weitere Wiki-Seite.</div>",
+ "chdate" : "1478854048",
+ "version" : "1",
+ "keyword" : "Weitere Seite",
+ "user" : {
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "id" : "205f3efb7997a0fc9755da2b535038da",
+ "name" : {
+ "family" : "Dozent",
+ "given" : "Testaccount",
+ "prefix" : "",
+ "suffix" : "",
+ "formatted" : "Testaccount Dozent",
+ "username" : "test_dozent"
+ },
+ "href" : "/api.php/user/205f3efb7997a0fc9755da2b535038da"
+ },
+ "content" : "Dies ist eine weitere Wiki-Seite.",
+ "range_id" : "a07535cf2f8a72df33c12ddfa4b53dde"
+}
+
+```
+
+
+
+##### `PUT` /course/:course_id/wiki/:seitenname
+
+Aktualisiert eine bestehende Wikiseite oder legt diese neu an. Der Parameter "content" muss gesetzt sein und den Inhalt der Wiki-Seite beinhalten.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| course_id | String, 32 Zeichen | Die ID der Veranstaltung
+| seitenname | String | Der Name der Wiki-Seite
+
+| **`PUT`-Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| content | String | Der neue Inhalt der Wiki-Seite
+
+
+##### `GET` /course/:course_id/wiki/:seitenname/:version
+
+Liefert eine bestimmte Version einer Wikiseite zurück.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| course_id | String, 32 Zeichen | Die ID der Veranstaltung
+| seitenname | String | Der Name der Wiki-Seite
+
+###### Antwortformat
+
+```json
+
+{
+ "range_id" : "a07535cf2f8a72df33c12ddfa4b53dde",
+ "content" : "Dies ist eine weitere Wiki-Seite, die mehrmals ge&#65533;ndert wurde!",
+ "version" : "1",
+ "content_html" : "<div class=\"formatted-content\">Dies ist eine weitere Wiki-Seite, die mehrmals ge&#65533;ndert wurde!</div>",
+ "chdate" : "1478854411",
+ "keyword" : "Weitere Seite",
+ "user" : {
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "name" : {
+ "prefix" : "",
+ "suffix" : "",
+ "family" : "Dozent",
+ "username" : "test_dozent",
+ "given" : "Testaccount",
+ "formatted" : "Testaccount Dozent"
+ },
+ "id" : "205f3efb7997a0fc9755da2b535038da",
+ "href" : "/api.php/user/205f3efb7997a0fc9755da2b535038da",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962"
+ }
+}
+
+```
+
+
+#### Blubber
+
+Neben den Methoden zum Holen aller Blubber eines Nutzers bzw. einer Veranstaltung gibt es die folgenden REST-Routen. Da Kommentare eines Blubbers ebenfalls wiederrum Blubber-Objekte sind, können alle Routen, die für Blubber-Objekte gedacht sind, auch für Blubber-Kommentare genutzt werden.
+
+
+##### `POST` /blubber/postings
+
+Erstellt einen neuen Blubber. Es müssen Parameter angegeben werden, damit ein neuer Blubber erstellt werden kann. Über den Parameter "content" wird der Inhalt des Blubbers gesetzt. Mittels "context_type" wird angegeben, ob der Blubber öffentlich (public), privat (private) oder zu einer Veranstaltung (course) gehört.
+
+Im Falle, dass der Blubber zu einer Veranstaltung gehört, muss der Parameter course_id gesetzt sein und dieser muss eine Veranstaltungs-ID enthalten.
+
+Falls der Blubber privat ist, muss über den Parameter "private_adresses" eine Liste mit Nutzer-IDs der Nutzer übergeben werden, welche den Blubber sehen dürfen. Hierbei ist zu beachten, das Nutzer, welche im Text des Blubbers mit einem @-Verweis referenziert wurden, den Blubber automatisch sehen können.
+
+
+| **`POST`-Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| content | String | Der Inhalt des Blubbers
+| context_type | String | Der Kontext, in dem ein Blubber erstellt wird: "course", "private", "public"
+| course_id | String, 32 Zeichen | Die Veranstaltungs-ID (nur erforderlich, wenn context_type = course gesetzt ist)
+| private_adresses | Array | Eine Liste mit Nutzer-IDs der Nutzer, die diesen Blubber sehen dürfen (nur erforderlich, wenn context_type = private gesetzt ist)
+
+
+##### `GET` /blubber/stream/:stream_id
+
+Liefert eine Liste der Blubber in einem bestimmten Blubber-Stream.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| stream_id | String, 32 Zeichen | Die ID des Blubber-Streams
+
+
+##### `GET` /blubber/posting/:blubber_id/comments
+
+Liefert eine Liste der Kommentare ("Antwort-Blubber") zu einem Blubber.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| blubber_id | String, 32 Zeichen | Die ID des Blubbers
+
+###### Antwortformat
+
+```json
+
+{
+ "collection" : {
+ "/api.php/blubber/comment/8340654c6fce2441e967ae3a9bf350eb" : {
+ "root_id" : "ecab929ef0dfaeca159802c018826a25",
+ "mkdate" : "1478854991",
+ "content" : "Test2",
+ "content_html" : "<div class=\"formatted-content\">Test2</div>",
+ "chdate" : "1478854991",
+ "author" : {
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "id" : "76ed43ef286fb55cf9e41beadb484a9f",
+ "name" : {
+ "given" : "Root",
+ "suffix" : "",
+ "prefix" : "",
+ "formatted" : "Root Studip",
+ "username" : "root@studip",
+ "family" : "Studip"
+ },
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962",
+ "href" : "/api.php/user/76ed43ef286fb55cf9e41beadb484a9f",
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962"
+ },
+ "blubber_id" : "8340654c6fce2441e967ae3a9bf350eb",
+ "context_type" : "public"
+ }
+ },
+ "pagination" : {
+ "total" : 1,
+ "limit" : 20,
+ "offset" : 0
+ }
+}
+
+```
+
+
+##### `POST` /blubber/posting/:blubber_id/comments
+
+Erstellt einen neuen Kommentar zu einem Blubber. Mittels des Parameters "content" wird der Inhalt des Kommentars gesetzt.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| blubber_id | String, 32 Zeichen | Die ID des Blubbers
+
+
+| **`POST`-Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| content | String | Der Inhalt des Blubbers
+
+
+##### `GET` /blubber/posting/:blubber_id und `GET` /blubber/comment/:blubber_id
+
+Liefert Daten zu einem Blubber oder einem Kommentar zurück. Das zurückgegebene JSON-Objekt ist in beiden Fällen gleich aufgebaut.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| blubber_id | String, 32 Zeichen | Die ID des Blubbers
+
+###### Antwortformat
+
+```json
+
+{
+ "mkdate" : "1478854991",
+ "chdate" : "1478854991",
+ "blubber_id" : "8340654c6fce2441e967ae3a9bf350eb",
+ "content_html" : "<div class=\"formatted-content\">Test2</div>",
+ "content" : "Test2",
+ "author" : {
+ "href" : "/api.php/user/76ed43ef286fb55cf9e41beadb484a9f",
+ "avatar_original" : "https://studip.example.org/pictures/user/nobody_original.png?d=0",
+ "avatar_normal" : "https://studip.example.org/pictures/user/nobody_normal.png?d=1476444962",
+ "id" : "76ed43ef286fb55cf9e41beadb484a9f",
+ "avatar_small" : "https://studip.example.org/pictures/user/nobody_small.png?d=1476444962",
+ "name" : {
+ "username" : "root@studip",
+ "suffix" : "",
+ "formatted" : "Root Studip",
+ "family" : "Studip",
+ "given" : "Root",
+ "prefix" : ""
+ },
+ "avatar_medium" : "https://studip.example.org/pictures/user/nobody_medium.png?d=1476444962"
+ },
+ "context_type" : "public",
+ "root_id" : "ecab929ef0dfaeca159802c018826a25"
+}
+
+```
+
+
+##### `PUT` /blubber/posting/:blubber_id und `PUT` /blubber/comment/:blubber_id
+
+Editiert einen Blubber. Über den mitgegebenen Parameter "content" kann der Inhalt des Blubbers geändert werden.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| blubber_id | String, 32 Zeichen | Die ID des Blubbers
+
+
+| **`PUT`-Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| content | String | Der neue Inhalt des Blubbers oder des Blubber-Kommentars
+
+
+##### `DELETE` /blubber/posting/:blubber_id und `DELETE` /blubber/comment/:blubber_id
+
+Löscht einen Blubber.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| blubber_id | String, 32 Zeichen | Die ID des Blubbers
+
+
+#### File - Dateien und Ordner
+
+##### `GET` /file/:file_id
+
+Liefert die Metadaten einer Datei bzw. eines Ordners zurück.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| file_id | String, 32 Zeichen | Die ID der Datei bzw. des Ordners
+
+
+###### Antwortformat
+
+```json
+
+{
+ "file_id" : "6b606bd3d6d6cda829200385fa79fcbf",
+ "chdate" : 1343924841,
+ "description" : "",
+ "range_id" : "ca002fbae136b07e4df29e0136e3bd32",
+ "protected" : false,
+ "content" : "/api.php/file/6b606bd3d6d6cda829200385fa79fcbf/content",
+ "mkdate" : 1343924827,
+ "downloads" : 0,
+ "course" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde",
+ "author" : "/api.php/user/76ed43ef286fb55cf9e41beadb484a9f",
+ "filesize" : 314146,
+ "filename" : "mappe_studip-el.pdf",
+ "name" : "Stud.IP-Produktbroschüre im PDF-Format"
+}
+
+```
+
+
+##### `GET` /file/:file_id/content
+
+Liefert den Inhalt einer Datei als Binärdaten zurück.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| file_id | String, 32 Zeichen | Die ID der Datei
+
+###### Antwortformat
+
+Die angeforderte Datei.
+
+
+##### `DELETE` /file/:file_id
+
+Löscht die Datei bzw. den Ordner.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| file_id | String, 32 Zeichen | Die ID der Datei bzw. des Ordners
+
+
+##### `PUT` /file/:file_id
+
+Aktualisiert die Datei bzw. den Ordner. Es können hierbei entweder nur die Metadaten über die Parameter "name" oder "description" (siehe unten) geändert werden oder aber bei Dateien eine neue Version der Datei angehängt werden. Hierbei ist zu beachten, dass die Anfrage als Multipart-Request ausgeführt werden muss.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| file_id | String, 32 Zeichen | Die ID der Datei bzw. des Ordners
+
+
+##### `POST` /file/:folder_id
+
+Erstellt eine Datei bzw. einen Ordner.
+
+Der Parameter "name" ist für einen neuen Ordner verpflichtend und gibt dessen Namen an. Bei Dateien kann dieser Parameter verwendet werden, um den Dateinamen zu überschreiben. Eine optionale Beschreibung kann über den Parameter "description" mitgeliefert werden.
+
+Sofern eine Datei erstellt werden soll, so muss diese als Multipart-Request angehängt werden, da ansonsten ein Ordner erstellt wird.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| folder_id | String, 32 Zeichen | Die ID der des Ordners, in dem eine neue Datei bzw. ein neuer Ordner erstellt werden soll
+
+
+| **`POST`-Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| name | String | Name der Datei bzw. des Ordners
+| (Datei) | binär | Daten der Datei als Multipart-Request
+
+
+#### Forum
+
+Ein Forum einer Veranstaltung bzw. einer Einrichtung ist aufgeteilt in mehrere Kategorien, welche Forenbeiträge besitzen. Forenbeiträge sind in einer Baumstruktur organisiert. Auf der obersten Ebene sind die Bereiche, z.B. "Allgemeine Diskussion". Unterhalb dieser sind die eigentlichen Themen als Kindelemente des "Bereichs-Beitrags" zu finden.
+
+##### `GET` /forum_category/:category_id
+
+Eine Kategorie eines Forums auslesen.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| category_id | String, 32 Zeichen | Die ID der Foren-Kategorie
+
+###### Antwortformat
+
+```json
+
+{
+ "areas_count" : 1,
+ "entry_name" : "Allgemein",
+ "seminar_id" : "a07535cf2f8a72df33c12ddfa4b53dde",
+ "areas" : "/api.php/forum_category/a07535cf2f8a72df33c12ddfa4b53dde/areas",
+ "category_id" : "a07535cf2f8a72df33c12ddfa4b53dde",
+ "course" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde",
+ "name" : "Allgemein",
+ "pos" : "0"
+}
+
+```
+
+
+##### `PUT` /forum_category/:category_id
+
+Eine Kategorie eines Forums aktualisieren. Über den Parameter "name" kann der Name der Kategorie geändert werden.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| category_id | String, 32 Zeichen | Die ID der Foren-Kategorie
+
+
+| **`PUT`-Parameter | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| name | String | Der neue Name der Kategorie
+
+
+##### `DELETE` /forum_category/:category_id
+
+Löscht eine Kategorie eines Forums.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| category_id | String, 32 Zeichen | Die ID der Foren-Kategorie
+
+
+##### `GET` /forum_category/:category_id/areas
+
+Liefert eine Liste mit Forenbeiträgen einer Forum-Kategorie zurück. Dabei werden nur die notwendigsten Daten des Forenbeitrags zurückgeliefert. Um mehr Daten zu einem Forenbeitrag zu erhalten, sollte die REST-Route /forum_entry/:entry_id aufgerufen werden (siehe unten).
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| category_id | String, 32 Zeichen | Die ID der Foren-Kategorie
+
+
+###### Antwortformat
+
+```json
+
+{
+ "collection" : {
+ "/api.php/forum_entry/fa431efbfa909ed48fbae10fef316222" : {
+ "course" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde",
+ "depth" : "1",
+ "content" : "Hier ist Raum für allgemeine Diskussionen",
+ "topic_id" : "fa431efbfa909ed48fbae10fef316222",
+ "subject" : "Allgemeine Diskussion",
+ "content_html" : "<div class=\"formatted-content\">Hier ist Raum für allgemeine Diskussionen</div>",
+ "user" : "/api.php/user/",
+ "anonymous" : "0",
+ "mkdate" : "1477315889",
+ "chdate" : "1477315889"
+ }
+ },
+ "pagination" : {
+ "total" : 1,
+ "offset" : 0,
+ "limit" : 20
+ }
+}
+
+```
+
+
+##### `POST` /forum_category/:category_id/areas
+
+Erstellt einen neuen Forenbeitrag. Der Parameter "subject" gibt das Thema des Beitrags an. Der Inhalt des Beitrags wird über den Parameter "content" gesetzt. Über den optionalen Parameter "anonymous" kann eingestellt werden, ob der Beitrag anonym erstellt werden soll. Dazu muss "anonymous" auf 1 gesetzt sein.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| category_id | string, 32 Zeichen | Die ID der Foren-Kategorie
+
+
+| **`POST`-Parameter | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| subject | String | Das Thema des Forenbeitrags
+| content | String | der Inhalt des Forenbeitrags
+| anonymous | Integer | Gibt an, ob der Eintrag anonym gemacht werden soll oder nicht: 1 = anonym, 0 = nicht anonym
+
+
+##### `GET` /forum_entry/:entry_id
+
+Liefert Daten zu einem Forenbeitrag zurück, inklusive dessen Inhalt.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| entry_id | String, 32 Zeichen | Die ID des Foren-Beitrags
+
+###### Antwortformat
+
+```json
+
+{
+ "topic_id" : "fa431efbfa909ed48fbae10fef316222",
+ "user" : "/api.php/user/",
+ "chdate" : "1477315889",
+ "subject" : "Allgemeine Diskussion",
+ "content_html" : "<div class=\"formatted-content\">Hier ist Raum f&#65533;r allgemeine Diskussionen</div>",
+ "children" : [
+ {
+ "content_html" : "<div class=\"formatted-content\"><div>Test-Inhalt des Test-Themas.</div></div>",
+ "subject" : "<div class=\"formatted-content\">Test-Thema</div>",
+ "chdate" : "1477316401",
+ "user" : "/api.php/user/76ed43ef286fb55cf9e41beadb484a9f",
+ "topic_id" : "d4dc64e82387ad5df7d388377e70a54b",
+ "anonymous" : "0",
+ "depth" : "2",
+ "mkdate" : "1477316401",
+ "content" : "<div class=\"formatted-content\">Test-Inhalt des Test-Themas.</div>",
+ "course" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde"
+ }
+ ],
+ "content" : "Hier ist Raum f&#65533;r allgemeine Diskussionen",
+ "course" : "/api.php/course/a07535cf2f8a72df33c12ddfa4b53dde",
+ "mkdate" : "1477315889",
+ "depth" : "1",
+ "anonymous" : "0"
+}
+
+```
+
+
+##### `PUT` /forum_entry/:entry_id
+
+Aktualisiert einen Forenbeitrag. Der Parameter "subject" setzt den Titel, der Parameter "content" setzt den Inhalt.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| entry_id | String, 32 Zeichen | Die ID des Foren-Beitrags
+
+
+| **`PUT`-Parameter | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| subject | String | Das Thema des Forenbeitrags
+| content | String | Der Inhalt des Forenbeitrags
+
+
+##### `DELETE` /forum_entry/:entry_id
+
+Löscht einen Forenbeitrag.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| entry_id | String, 32 Zeichen | Die ID des Foren-Beitrags
+
+
+##### `POST` /forum_entry/:entry_id
+
+Fügt einem Forenbeitrag einen neuen Forenbeitrag (eine neue Antwort) hinzu.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| entry_id | string, 32 Zeichen | Die ID des Foren-Beitrags
+
+
+| **`POST`-Parameter | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| subject | String | Das Thema des Forenbeitrags
+| content | String | der Inhalt des Forenbeitrags
+| anonymous | Integer | Gibt an, ob der Beitrag anonym angezeigt werden soll oder nicht: 1 = anonym, 0 = nicht anonym
+
+
+#### Nachrichten
+
+##### `POST` /messages
+
+Schreibt eine neue Nachricht. Der Parameter "subject" setzt den Titel, während der Parameter "message" den Inhalt der Nachricht setzt. Zudem müssen die Empfänger der Nachricht ebenfalls angegeben werden. Dazu dient der Parameter "recipients", dem die Nutzer-IDs der Empfänger übergeben werden.
+
+
+| **`POST`-Parameter | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| subject | String | Das Thema der Nachricht
+| message | String | Der Inhalt der Nachricht
+| recipients | Array | Die Nutzer-IDs der Empfänger der Nachricht
+
+
+##### `GET` /message/:message_id
+
+Liefert eine Nachricht zurück.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| message_id | String, 32 Zeichen | Die ID der Nachricht
+
+
+##### `PUT` /message/:message_id
+
+Mit dieser Route kann eine Nachricht als ungelesen oder gelesen markiert werden oder sie kann in einen anderen Nachrichtenordner verschoben werden.
+
+Zum Markieren der Nachricht als ungelesen muss der Parameter "unread" auf 1 gesetzt sein. Für den umgekehrten Fall wird der Parameter auf 0 gesetzt.
+
+Zum Verschieben der Nachricht in einen anderen Nachrichtenordner muss die Nutzer-ID, die ID des Ordners und der Nachrichtenbereich angegeben werden, sodass ein Pfad folgender Struktur angegeben werden muss:
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| message_id | String, 32 Zeichen | Die ID der Nachricht
+
+
+| **`PUT`-Parameter | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| unread | Integer | Gibt an, ob die Nachricht als ungelesen markiert werden soll (1) oder als gelesen markiert werden soll (0)
+
+Die Parameter können dabei sowohl über ein JSON-Objekt im Body mit gesetztem `Content-Type: application/json` oder wie bei `POST`-Requests abgesetzt werden. Explizit kann der Parameter nicht über `GET`-Parameter gesetzt werden.
+
+##### `DELETE` /message/:message_id
+
+Löscht eine Nachricht.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| message_id | String, 32 Zeichen | Die ID der Nachricht
+
+
+#### Semester
+
+##### `GET` /semesters
+
+Liefert eine Liste aller Semester zurück.
+
+###### Antwortformat
+
+```json
+
+{
+ "pagination" : {
+ "offset" : 0,
+ "limit" : 20,
+ "total" : 2
+ },
+ "collection" : {
+ "/api.php/semester/eb828ebb81bb946fac4108521a3b4697" : {
+ "begin" : 1475272800,
+ "end" : 1490997599,
+ "seminars_end" : 1486162799,
+ "title" : "WS 2016/17",
+ "description" : "",
+ "id" : "eb828ebb81bb946fac4108521a3b4697",
+ "seminars_begin" : 1476655200
+ },
+ "/api.php/semester/f2b4fdf5ac59a9cb57dd73c4d3bbb651" : {
+ "description" : "",
+ "title" : "SS 2016",
+ "seminars_begin" : 1460325600,
+ "id" : "f2b4fdf5ac59a9cb57dd73c4d3bbb651",
+ "seminars_end" : 1468619999,
+ "end" : 1475272799,
+ "begin" : 1459461600
+ }
+ }
+}
+
+```
+
+
+##### `GET` /semester/:semester_id
+
+Liefert ein einzelnes Semester zurück. Diese REST-Route liefert die gleiche Menge an Daten zu einem Semester, wie der Aufruf der Route /semesters.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| semester_id | String, 32 Zeichen | Die ID des Semesters
+
+###### Antwortformat
+
+```json
+
+{
+ "begin" : 1459461600,
+ "title" : "SS 2016",
+ "seminars_begin" : 1460325600,
+ "seminars_end" : 1468619999,
+ "end" : 1475272799,
+ "id" : "f2b4fdf5ac59a9cb57dd73c4d3bbb651",
+ "description" : ""
+}
+
+```
+
+
+#### Ankündigungen
+
+Die Routen zu Ankündigungen eines Nutzers, einer Veranstaltung oder des Stud.IP Systems sind in den jeweiligen Abschnitten oberhalb dieses Abschnittes beschrieben.
+
+##### `GET` /news/:news_id
+
+Liefert eine Ankündigung zurück.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| news_id | String, 32 Zeichen | Die ID der Ankündigung
+
+###### Antwortformat
+
+```json
+
+{
+ "news_id" : "29f2932ce32be989022c6f43b866e744",
+ "chdate" : "1476445862",
+ "comments" : "/api.php/news/29f2932ce32be989022c6f43b866e744/comments",
+ "expire" : "14562502",
+ "mkdate" : "1476445862",
+ "topic" : "Herzlich Willkommen!",
+ "chdate_uid" : "",
+ "allow_comments" : "1",
+ "comments_count" : 1,
+ "user_id" : "76ed43ef286fb55cf9e41beadb484a9f",
+ "ranges" : [
+ "/api.php/user/76ed43ef286fb55cf9e41beadb484a9f/news",
+ "/api.php/studip/news"
+ ],
+ "body" : "Das Stud.IP-Team heisst sie herzlich willkommen. \r\nBitte schauen Sie sich ruhig um!\r\n\r\nWenn Sie das System selbst installiert haben und diese News sehen, haben Sie die Demonstrationsdaten in die Datenbank eingefügt. Wenn Sie produktiv mit dem System arbeiten wollen, sollten Sie diese Daten später wieder löschen, da die Passwörter der Accounts (vor allem des root-Accounts) öffentlich bekannt sind.",
+ "body_html" : "<div class=\"formatted-content\">Das Stud.IP-Team heisst sie herzlich willkommen. <br>Bitte schauen Sie sich ruhig um!<br><br>Wenn Sie das System selbst installiert haben und diese News sehen, haben Sie die Demonstrationsdaten in die Datenbank eingefügt. Wenn Sie produktiv mit dem System arbeiten wollen, sollten Sie diese Daten später wieder löschen, da die Passwörter der Accounts (vor allem des root-Accounts) öffentlich bekannt sind.</div>",
+ "date" : "1476445862"
+}
+
+```
+
+
+##### `PUT` /news/:news_id
+
+Aktualisiert eine Ankündigung. Über den Parameter "topic" kann der Titel der Ankündigung gesetzt werden, während über den Parameter "body" der Inhalt der Ankündigung verändert werden kann.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| news_id | String, 32 Zeichen | Die ID der Ankündigung
+
+
+| **`PUT`-Parameter | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| topic | String | Der Titel der Ankündigung
+| body | String | Der Inhalt der Ankündigung
+| expire | Integer | Ablaufdatum der Nachricht (in Sekunden vom aktuellen Datum gerechnet)
+| allow_comments | Integer | Gibt an, ob Kommentare erlaubt sind: 1 = erlaubt, 0 = nicht erlaubt
+
+
+##### `DELETE` /news/:news_id
+
+Löscht eine Ankündigung.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| news_id | String, 32 Zeichen | Die ID der Ankündigung
+
+
+##### `GET` /news/:news_id/comments
+
+Liefert eine Liste der Kommentare zu einer Ankündigung zurück.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| news_id | String, 32 Zeichen | Die ID der Ankündigung
+
+###### Antwortformat
+
+```json
+
+{
+ "collection" : {
+ "/api.php/comment/a97b1b623d92f11293d4a8de52724087" : {
+ "chdate" : "1477320607",
+ "comment_id" : "a97b1b623d92f11293d4a8de52724087",
+ "object_id" : "29f2932ce32be989022c6f43b866e744",
+ "content" : "Test-Kommentar",
+ "content_html" : "Test-Kommentar",
+ "mkdate" : "1477320607",
+ "user_id" : "76ed43ef286fb55cf9e41beadb484a9f"
+ }
+ },
+ "pagination" : {
+ "total" : 1,
+ "limit" : 20,
+ "offset" : 0
+ }
+}
+
+```
+
+
+##### `POST` /news/:news_id/comments
+
+Erstellt einen neuen Kommentar zu einer Ankündigung. Der Inhalt des Kommentars wird über den Parameter "content" gesetzt.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| news_id | String, 32 Zeichen | Die ID der Ankündigung
+
+
+| **`POST`-Parameter | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| content | String | Der Inhalt des Kommentars
+
+
+##### `GET` /comment/:comment_id
+
+Liest einen Kommentar zu einer Ankündigung aus.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| news_id | String, 32 Zeichen | Die ID des Kommentars
+
+###### Antwortformat
+
+```json
+
+{
+ "content" : "Test-Kommentar",
+ "comment_id" : "a97b1b623d92f11293d4a8de52724087",
+ "content_html" : "<div class=\"formatted-content\">Test-Kommentar</div>",
+ "chdate" : "1477320607",
+ "author" : "/api.php/user/76ed43ef286fb55cf9e41beadb484a9f",
+ "mkdate" : "1477320607",
+ "news" : "/api.php/news/29f2932ce32be989022c6f43b866e744"
+}
+
+```
+
+
+##### `DELETE` /comment/:comment_id
+
+Löscht einen Kommentar zu einer Ankündigung.
+
+
+| **Parameter** | **Format** | **Beschreibung**
+| ---- | ---- | ---- |
+| news_id | String, 32 Zeichen | Die ID des Kommentars
+
+
+### Erweiterung der REST-API im Kern
+
+Zur Implementierung neuer REST-Routen im Kern muss eine Klasse geschrieben werden, welche den Namespace \RESTAPI\Routes nutzt und die Klasse \RESTAPI\RouteMap erweitert. In der Klasse RouteMap (lib/classes/restapi/RouteMap.php) ist in den Kommentaren eine umfangreiche Beschreibung vorhanden, welche erklärt, wie REST-Routen erzeugt werden können.
+
+
+Die Datei, welche die neue Klasse beinhaltet muss unter app/routes/ liegen, den gleichen Namen wie die enthaltene Klasse tragen und ihr Name muss in der Klasse Router (lib/classes/restapi/Router.php) in der Methode setupRoutes() eingetragen werden. Innerhalb dieser Methode befindet sich folgende Codezeile:
+
+```php
+$routes = words('Activity Blubber Contacts ...');
+```
+
+Innerhalb der Zeichenkette, welche words übergeben wird, muss der Name der Datei mit der neuen REST-API-Klasse eingetragen werden.
+
+
+### Erweiterung der REST-API durch Plugins
+
+Damit die REST-API auch durch Plugins erweitert werden kann, wurde die neuen Pluginklasse `RESTAPIPlugin` erschaffen. Plugins dieser Art müssen eine einzige Methode names `getRouteMaps()` implementieren, welche alle REST-API Routen (RouteMaps genannt) zurückliefert, die das Plugin implementiert.
+
+#### Struktur REST-API Plugin
+
+Zum Bereitstellen von Routen muss das Plugin eine Klasse enthalten, die die Klasse RouteMap erweitert. In dieser muss mindestens die Methode `before` implementiert sein. Für einfache Routen kann diese Methode leer gelassen werden.
+
+Im Anschluss müssen Methoden für die zu implementierenden Routen geschrieben werden. Eine Methode kann hierbei mehrere Routen implementieren. Durch den Kommentar überhalb einer Methode werden die Routen angegeben, die zum Aufruf der Methode führen. Dabei können auch Parameter und Parameterbedingungen im Kommentar definiert werden. Ein Beschreibungstext zur Route muss auch in den Kommentar hinein.
+
+#### Ausgeben von Daten
+
+Daten, welche über die API ausgegeben werden sollen, werden einfach von der Methode zurückgegeben (via return). Dabei kann es sich um einfache Strings, Zahlen oder Objekte handeln.
+
+Zum Versenden einer Liste von Objekten über die API sollte die Methode `paginated` verwendet werden, wie folgendes Beispiel zeigt:
+
+```php
+return $this->paginated($data, $total, $uriParameters);
+```
+
+$data enthält die zu sendenden Daten, $total die Anzahl der Datensätze (gesamter Datenbestand) und $uriParameters notwendige Parameter, die bei der Generierung von URIs für die aktuelle Route benötigt werden. Die Parameter "offset" und "limit" werden von der RouteMap-Klasse bereits intern behandelt und müssen nicht extra angegeben werden.
+
+
+#### Überschreiben von Kernrouten
+
+Es ist möglich, vorhandene Kernrouten zu überschreiben. Dabei sollte aber in jedem Fall sichergestellt sein, dass die Rückgabe dem eigentlichen Format entspricht.
+
+#### Beispiel eines REST-API Plugins
+
+Das folgende Beispiel zeigt ein REST-API Plugin, welches eigene API-Routen implementiert:
+
+Zuerst die Plugin-Klasse:
+
+```php
+<?php
+/**
+ * HelloAPIPlugin.class.php
+ *
+ * @author Jan-Hendrik Willms <tleilax+studip@gmail.com>
+ * @author Moritz Strohm <strohm@data-quest.de>
+ * @version 1.0
+ */
+
+require_once __DIR__ . '/HelloMap.class.php';
+
+class HelloAPIPlugin extends StudIPPlugin implements RESTAPIPlugin
+{
+ public function getRouteMaps()
+ {
+ return new HelloMap();
+ }
+}
+```
+
+Die Plugin-Klasse liefert beim Aufruf der Methode getRouteMaps einfach eine Instanz der HelloMap-Klasse zurück.
+
+Die HellpMap-Klasse sieht folgendermaßen aus:
+
+```php
+class HelloMap extends \RESTAPI\RouteMap
+{
+ // Called before the route is executed
+ public function before() {}
+
+ /**
+ * Greets the caller
+ *
+ * @`GET` /hello
+ * @`GET` /hello/:name
+ * @condition name ^\w+$
+ */
+ public function sayHello($name = 'world')
+ {
+ return sprintf('Hello %s!', $name);
+ }
+
+ // Called after the route is executed
+ public function after() {}
+}
+```
+
+Über die HelloMap-Klasse stellt das Plugin die Routen /hello und /hello:name bereit. Der Aufruf dieser beiden Routen führt zum Aufruf der Methode `sayHello`. Ist der Parameter "name" gesetzt, so wird der entsprechende Name zurückgeliefert.
diff --git a/docs/docs/rules/fehler-melden.md b/docs/docs/rules/fehler-melden.md
new file mode 100644
index 0000000..a56a824
--- /dev/null
+++ b/docs/docs/rules/fehler-melden.md
@@ -0,0 +1,104 @@
+---
+title: Fehler berichten
+---
+
+Für Meldung und Verwaltung von Fehlern wird das [Gitlab](http://gitlab.studip.de) auf dem Entwicklungsserver verwendet.
+Dort sind alle bekannten und behobenen Fehler dokumentiert, und auch die Planung für die zukünftigen Stud.IP-Releases wird dort organisiert.
+
+## Wie berichte ich einen Fehler?
+
+Für die Meldung von Fehlern in Stud.IP gibt es verschiedene Wege:
+
+* Direkt über das Formular im [Gitlab](https://gitlab.studip.de/studip/studip/-/issues/new) (Registrierung im Entwickler- und Anwenderforum erforderlich)
+* Im Developer-Board im Forum berichten [Veranstaltung Bugboard (BIESTs)](https://develop.studip.de/studip/dispatch.php/course/forum/index?cid=28e888802838a57bc1fbac4e39f0b13a) im [Entwickler- und Anwenderforum](https://develop.studip.de/studip/) (Registrierung erforderlich)
+* per E-Mail an [studip-users@lists.sourceforge.net](mailto:studip-users@lists.sourceforge.net)
+* Oder im [Entwickler-Chat](https://develop.studip.de/studip/assets/images/icons/blue/chat.svg) uns mitteilen.
+
+**Bitte geben Sie in jedem Fall unbedingt an:**
+
+* In welcher Stud.IP-Version bzw. an welchem Standort tritt der Fehler auf? (z.B.: Version 5.4, Uni Göttingen)
+* Welchen Browser in welcher Version verwenden Sie? (z.B.: Internet Explorer 7)
+* In welcher Rolle sind Sie im System unterwegs? (z.B. "Dozent")
+* Beschreiben Sie möglichst genau, was unter welchen Umständen getan werden muß, um den Fehler zu reproduzieren.
+
+### Fehlerbericht über das gitlab
+
+Der "normale" Weg, einen Fehler in Stud.IP zu melden, ist die Verwendung des entsprechenden Formulars im gitlab: https://gitlab.studip.de/studip/studip/-/issues/new
+
+Beachten Sie, dass es für die Entwickelnden hilfreich ist, wenn Sie möglichst präzise Angaben zu dem Fehler machen.
+
+- Was genau muss man tun, um den Fehler reproduzieren zu können?
+- Tritt der Fehler in jedem Browser auf oder nur in manchen (Chrome, Firefox, Safari, Edge)
+- In welcher Version von Stud.IP tritt der Fehler auf (Ihre Stud.IP-Version steht meist im Impressum)?
+- Idealerweise laden Sie einen Screenshot mit hoch, wo auch die Adresszeile des Browsers sichtbar ist.
+
+
+### Fehlerbericht via E-Mail
+
+Alternativ können Fehler auch per Mail an die Adresse [studip-users@lists.sourceforge.net](mailto:studip-users@lists.sourceforge.net) berichtet werden. Diese landen allerdings nicht automatisch in unserem Stud.IP-Ticketsystem, es kann daher gelegentlich passieren, daß diese längere Zeit unbearbeitet sind oder sogar wieder ganz in Vergessenheit geraten.
+
+Daher sollten Fehler, wenn möglich, nicht via E-Mail berichtet werden, sondern über den unter 1.1 oder 1.2 genannten Weg.
+
+## Wie berichte ich einen Verbesserungsvorschlag?
+
+Die erste Anlaufstelle für Erweiterungs- oder Verbesserungsvorschläge sollte das Forum der Veranstaltung [Developer-Board](https://develop.studip.de/studip/forum.php?cid=a70c45ca747f0ab2ea4acbb17398d370&view=tree) im [Entwickler- und Anwenderforum](https://develop.studip.de/studip/) sein. Dort kann man mit den Stud.IP-Entwicklern diskutieren, ob und ggf. in welcher Forum die eigenen Ideen umgesetzt werden könnten. Verbesserungsvorschläge sollten (von Ausnahmefällen abgesehen) nicht ohne vorherige Diskussion ins gitlab eingetragen werden.
+
+### Typen von Tickets
+
+Es gibt folgende Ticket-Typen:
+
+| Typ | Beschreibung |
+| ---- | ---- |
+| BIEST | ein Fehler im offiziellen Release |
+| Lifters | eine langfristig angelegte Überarbeitung, muß zuvor von der Core-Group abgestimmt werden |
+| StEP | ein Verbesserungsvorschlag, muß zuvor von der Core-Group abgestimmt werden |
+| TIC | ein "kleiner" Verbesserungsvorschlag |
+
+
+### Milestone
+// TODO: muss überarbeitet werden
+
+Ein Milestone im gitlab entspricht jeweils einem offiziellen Release von Stud.IP (wie 5.3 oder 5.4) und wird als ein Label abgebildet.
+Über die Milestone-Angabe im Ticket wird verwaltet, welche Tickets für welches Stud.IP-Release erfolgreich geschlossen worden sind oder noch erledigt werden müssen. Nur Tickets, die sich auf das offizielle Release beziehen, haben einen Milestone, und der Milestone sollte nur von der Person bearbeitet werden, der das Ticket zugewiesen ist (oder einem der Release-Verantwortlichen). Dabei gelten folgende Regeln:
+
+| Typ | Beschreibung |
+| ---- | ---- |
+| **BIEST** | Ein offenes BIEST hat keinen Milestone. Wenn das BIEST geschlossen wird, gibt der Milestone die erste Version von Stud.IP an, die diese Korrektur enthält, das ist in der Regel der jeweils aktuelle Release-Branch. |
+| **StEP**, **TIC** | Der Milestone ist die Version, für die der StEP bzw. TIC eingebaut werden soll. |
+| **Lifters**| Ein Lifters hat keinen Milestone. |
+
+**Wichtig**: Der Milestone gibt *nicht* an, in welcher Version der Fehler aufgetreten ist. Das sollte Teil des Beschreibungstextes sein.
+
+
+### Zusätzliche Felder zur Qualitätssicherung
+
+Bei Tickets der Typen StEP und TIC gibt es zusätzliche Felder, die der Qualitätssicherung durch die Coregroup dienen und an die entsprechend mit Veto-Vollmacht ausgestatteten Zuständigkeiten gekoppelt sind. Derzeit werden folgende Felder verwendet:
+
+| Typ| Beschreibung |
+| ---- | --- |
+| **Code-Qualität?** | Code-Review erwünscht |
+| **Code-Qualität+** | Code-Review positiv |
+| **Code-Qualität-** | Code-Review negativ, d.h. Veto des Verantwortlichen |
+| **Sicherheit?** | Security-Review erwünscht |
+| **Sicherheit+** | Security-Review positiv |
+| **Sicherheit-** | Security-Review negativ, d.h. Veto des Verantwortlichen |
+| **Code-Konventionen?** | Review der formalen Code-Konventionen erwünscht |
+| **Code-Konventionen+** | Review der formalen Code-Konventionen positiv |
+| **Code-Konventionen-** | Review der formalen Code-Konventionen negativ, d.h. Veto des Verantwortlichen |
+| **Entwickler-Dokumentation?** | Entwicklerdokumentations-Review erwünscht |
+| **Entwickler-Dokumentation+** | Entwicklerdokumentations-Review positiv |
+| **Entwickler-Dokumentation-** | Entwicklerdokumentations-Review negativ, d.h. Veto des Verantwortlichen |
+| **Anwender-Dokumentation?** | Anwenderdokumentations-Review erwünscht |
+| **Anwender-Dokumentation+** | Anwenderdokumentations-Review positiv |
+| **Anwender-Dokumentation-** | Anwenderdokumentations-Review negativ, d.h. Veto des Verantwortlichen |
+| **Funktionalität?** | Funktionstest aus Anwendersicht-Review erwünscht |
+| **Funktionalität+** | Funktionstest aus Anwendersicht-Review positiv |
+| **Funktionalität-** | Funktionstest aus Anwendersicht-Review negativ, d.h. Veto des Verantwortlichen |
+| **GUI-Richtlinien?** | Review bezüglich der Nutzeroberfläche erwünscht |
+| **GUI-Richtlinien+** | Review bezüglich der Nutzeroberfläche positiv |
+| **GUI-Richtlinien-** | Review bezüglich der Nutzeroberfläche negativ, d.h. Veto des Verantwortlichen |
+
+
+## Keywords
+
+Über die Standardattribute hinausgehende Markierungen an einem Ticket werden über Keywords abgebildet.
diff --git a/docs/docs/rules/gitlab-workflows.md b/docs/docs/rules/gitlab-workflows.md
new file mode 100644
index 0000000..28791a7
--- /dev/null
+++ b/docs/docs/rules/gitlab-workflows.md
@@ -0,0 +1,90 @@
+---
+id: gitlab-workflows
+title: Unsere Workflows in GitLab
+sidebar_label: Gitlab-Workflows
+---
+
+tl;dr
+
+- Jeder Commit auf den Branch `main` muss eine Referenz auf ein Issue beinhalten (`..., fixes #23` oder `..., re #23`).
+- Jede Änderung sollte über einen Merge Request in den Branch `main` gelangen (Ausnahmen sind triviale Änderungen wie beispielsweise Rechtschreibfehler).
+- Jeder Merge Request sollte von mindestens einer weiteren Person angeschaut und genehmigt werden.
+
+## BIESTs
+
+Für Bugreports und Bugfixes in Stud.IP (a.k.a. [BIESTs](Regeln#biests)) gelten die folgenden Regeln:
+
+- Es muss ein Issue im [Stud.IP-Projekt](https://gitlab.studip.de/studip/studip) erstellt werden.
+- Dieses Issue muss das Label "BIEST" haben sowie eines der Labels "Version::xy", das die niedrigste Version angibt, in der der Fehler auftaucht. Wenn dies nicht bekannt ist, kann das Label weggelassen werden. Dann sollte aber die Version eingetragen werden, in der das Problem beobachtet wurde.
+- Optional kann (und sollte) eines der Label "Komponente::xy" eingetragen werden, um die Issues nach Komponente filtern zu können.
+- Für Bugfixes sollte ein Merge Request erstellt werden, der das Ticket referenziert. Ausnahmen sind triviale Änderungen wie Rechtschreibfehler, die direkt auf den Branch `main` geschoben werden können.
+- Die Merge Requests sollten dabei von einem Branch im Hauptprojekt erstellt werden und einer der beiden folgenden Notationen folgen:
+ <ul><li><code>biest-&lt;nummer&gt;</code></li><li><code>&lt;nummer&gt;-beschreibung</code></li></ul>
+ Bevorzugt wird die erste Variante, da sie klar erkennen lassen, worauf sich der entsprechende Branch bezieht. Die zweite Variante ist die, die von gitlab erzeugt wird, wenn zu einem Issue über die GUI ein Merge Request erstellt wird.
+- Wenn das Problem behoben ist, muss das Ticket geschlossen werden, damit die Maintainer des Projektes erkennen können, dass der Bugfix noch in andere Versionen transportiert werden muss.
+- Erst wenn der Bugfix in alle notwendigen Versionen verteilt wurde, wird der Meilenstein am Issue gesetzt.
+
+Eine [Übersicht aller offenen Issues](https://gitlab.studip.de/studip/studip/-/issues?scope=all&state=opened&label_name%5B%5D=BIEST) erhält man, wenn in der Übersicht der Issues nach offenen Issues mit dem Label "BIEST" filtert.
+
+## StEPs
+
+Für Stud.IP-Enhancement-Proposals ([StEPs](Regeln#steps)) gelten die folgenden Regeln:
+
+- Es muss ein Issue im [Stud.IP-Projekt](https://gitlab.studip.de/studip/studip) erstellt werden.
+- Dieses Issue muss das Label "StEP" haben und als Meilenstein muss die Version von Stud.IP angegeben werden, für die der StEP entwickelt wird.
+- Optional kann eines der Label "Komponente::xy" angegeben werden falls sich der StEP auf eine Komponente bezieht.
+- Die Entwicklung des StEPs geschieht in einem Branch, der idealerweise am Hauptprojekt hängen sollte (aber nicht muss) und einer der beiden folgenden Notationen folgen muss:
+ <ul><li><code>step-&lt;nummer&gt;</code></li><li><code>&lt;nummer&gt;-beschreibung</code></li></ul>
+ Bevorzugt wird die erste Variante, da sie klar erkennen lassen, worauf sich der entsprechende Branch bezieht. Die zweite Variante ist die, die von gitlab erzeugt wird, wenn zu einem Issue über die GUI ein Merge Request erstellt wird.
+- Idealerweise sollte frühzeitig ein Merge Request erstellt werden. Solange der StEP in Entwicklung ist, sollte der Merge Request den "Draft"-Status haben, damit man schnell erfassen kann, welche StEPs sich noch in Entwicklung befinden und welche bereits abgeschlossen sind.
+- Ist die Entwicklung beendet, sollten die relevanten Label für das Qualitätsmanagement am Issue gesetzt werden, damit die Qualitätsbeauftragten mit dem Review beginnen können. Das Codereview erfolgt am Merge Request und erst, wenn der Merge Request genehmigt wurde, kann das entsprechende Label am Issue geändert werden.
+- Für jeden StEP sollte frühzeitig ein Testsystem zur Verfügung gestellt werden, damit die Qualitätsbeauftragten dort testen können.
+- Hat ein StEP alle relevanten Qualitätstests durchlaufen, kann der Merge Request in das Hauptsystem (also den Branch `main`) überführt werden.
+- Das Issue kann geschlossen werden sobald der Merge Request überführt wurde.
+- Werden bei den neu implementierten Funktionen des StEPs Fehler entdeckt und das Issue ist bereits geschlossen, müssen diese als BIEST eingetragen. Das Issue für den StEP muss dann als "Linked Issue" eingetragen werden. Dies geschieht über die GUI, indem man in einem Issue unterhalb des Beschreibungstextes die entsprechende Funktion aufruft. Solange das Issue des StEPs noch offen ist, können Fehler auch dort als Kommentar berichtet werden.
+
+## TICs
+
+Für _Tiny Improvement Commits_ ([TICs](Regeln#tics)) gelten die folgenden Regeln:
+
+- Es muss ein Issue im [Stud.IP-Projekt](https://gitlab.studip.de/studip/studip) erstellt werden.
+- Dieses Issue muss das Label "TIC" haben und als Meilenstein muss die Version von Stud.IP angegeben werden, für die der TIC entwickelt wird.
+- Optional kann eines der Label "Komponente::xy" angegeben werden falls sich der TIC auf eine Komponente bezieht.
+- Die Entwicklung des TIC geschieht in einem Branch, der am Hauptprojekt hängen sollte und einer der beiden folgenden Notationen folgen:
+ <ul><li><code>tic-&lt;nummer&gt;</code></li><li><code>&lt;nummer&gt;-beschreibung</code></li></ul>
+ Bevorzugt wird die erste Variante, da sie klar erkennen lassen, worauf sich der entsprechende Branch bezieht. Die zweite Variante ist die, die von gitlab erzeugt wird, wenn zu einem Issue über die GUI ein Merge Request erstellt wird.
+- Idealerweise sollte frühzeitig ein Merge Request erstellt werden. Solange der TIC in Entwicklung ist, sollte der Merge Request den "Draft"-Status haben, damit man schnell erfassen kann, welche TICs sich noch in Entwicklung befinden und welche bereits abgeschlossen sind.
+- Ist die Entwicklung beendet, sollten die relevanten Label für das Qualitätsmanagement am Issue gesetzt werden, damit die Qualitätsbeauftragten mit dem Review beginnen können. Das Codereview erfolgt am Merge Request und erst, wenn der Merge Request genehmigt wurde, kann das entsprechende Label am Issue geändert werden.
+- Wurde der Merge Request für den TIC akzeptiert, kann er ab diesem Zeitpunkt in das Hauptsystem (also den Branch `main`) überführt werden.
+- Das Issue kann geschlossen werden sobald der Merge Request überführt wurde.
+- Werden bei den neu implementieren Funktionen des TICs Fehler entdeckt und das Issue ist bereits geschlossen, müssen diese als BIEST eingetragen. Das Issue für den TIC muss dann als "Linked Issue" eingetragen werden. Dies geschieht über die GUI, indem man in einem Issue unterhalb des Beschreibungstextes die entsprechende Funktion aufruft. Solange das Issue des TICs noch offen ist, können Fehler auch dort als Kommentar berichtet werden.
+
+## Lifters
+
+Für _Laufende, inkrementell fortschreitende Technikrenovierungen für Stud.IP_ ([Lifters](Regeln#lifters)) gelten die folgenden Regeln:
+
+- Es muss ein Issue im [Stud.IP-Projekt](https://gitlab.studip.de/studip/studip) erstellt werden.
+- Dieses Issue muss das Label "LifTer" haben. Der Meilenstein bleibt leer, da sich ein Lifters selten innerhalb einer einzigen Version von Stud.IP abschliessen lässt.
+- Optional kann eines der Label "Komponente::xy" angegeben werden falls sich der Lifters auf eine Komponente bezieht.
+- Die Entwicklung des Lifters erfolgt nun in jeweils einzelnen Issues, die den Regeln von TICs folgen. Die jeweiligen Issues müssen über die Linked Issues mit dem Issue des Lifters verknüft werden, damit man eine Übersicht der für den Lifters erfolgten Änderungen in einem Issue hat.
+
+## Pipelines / CI/CD
+
+Für jeden Commit in jedem Branch des Hauptprojekts wird eine Pipeline angestossen, die folgende Aktionen ausführt:
+
+- Linting/Syntax Check
+- Ausführen der Unit Tests
+
+In GitLab können die Ergebnisse der Ausführungen der Pipeline unter dem Punkt [CI/CD](https://gitlab.studip.de/studip/studip/-/pipelines) abgefragt werden. Gibt es zu einem Branch einen Merge Request, so ist das Ergebnis auch dort sichtbar. Schlägt eine Pipeline für den Branch eines Merge Requests fehl, so darf dieser Branch nicht in den Branch `main` überführt werden.
+
+Die Pipelines befinden sich aktuell noch im Aufbau und werden laufend erweitert.
+
+Das Releasemanagement erfolgt auch über eine Pipeline, die angestossen wird, wenn ein entsprechendes Releasetag erstellt wird.
+
+## Mergen von Änderungen in andere Versionen
+
+Eine Übersicht der noch zu transportierenden Bugfixes findet man entweder über die Übersicht der Issues in gitlab oder über das [Dashboard-Plugin im DevBoard](https://develop.studip.de/studip/plugins.php/tractogitlabplugin/merge). Die Überführung der Änderungen erfolgt dabei mittels [Cherry Picking](https://www.atlassian.com/git/tutorials/cherry-pick) der Commits auf die entsprechenden Versionsbranches.
+
+Das Übertragen der Bugfixes kann von jeder Person vorgenommen werden, die Schreibrechte auf die entsprechenden Branches hat. Möchte man dies nicht tun, werden in unregelmässigen Abständen oder auf Zuruf sämtliche aufgelaufenen Änderungen gesammelt in die entsprechenden Versionen überführt.
+
+Beim Portieren der Bugfixes gilt die Regel, dass immer die aktuelle Version von Stud.IP sowie die beiden Versionen davor mit Bugfixes versorgt werden. \ No newline at end of file
diff --git a/docs/docs/rules/introduction.md b/docs/docs/rules/introduction.md
new file mode 100644
index 0000000..006d38e
--- /dev/null
+++ b/docs/docs/rules/introduction.md
@@ -0,0 +1,317 @@
+---
+id: introduction
+title: Regeln für die Stud.IP-Entwicklung
+sidebar_label: Entwicklungsregularien
+---
+
+Stud.IP ist Open-Source-Software. Offizielle Release-Versionen werden von der Stud.IP-Core-Group unter einer Open-Source-Lizenz veröffentlicht.
+
+# Test- und Entwicklungsserver
+
+Hauptkommunikationsinstrument für die Stud.IP-Entwicklung ist der Test- und Entwicklungsserver unter http://develop.studip.de. Jeder aktive Stud.IP-Entwickler soll diesen Server regelmäßig besuchen, um aktuelle Diskussionen verfolgen und eigene Fragen und Anregungen einbringen zu können.
+
+# Versionskontrolle und Ticket-Verwaltung
+
+Für die Stud.IP-Entwicklung wird eine git-Installation unterhalten, die jeweilige Hauptversion liegt unter https://gitlab.studip.de/studip/studip.git.
+Zur Ticketverwaltung wird Gitlab genutzt, das unter http://gitlab.studip.de erreichbar ist.
+
+## Vergabe und Nutzung von Schreibrechten
+
+Schreibrechte im svn werden auf Anfrage von den jeweils Zuständigen der Core-Group vergeben.
+Im main-Zweig und den Release-Zweigen dürfen nur Checkins vorgenommen werden, die den unten aufgeführten Regelungen entsprechen.
+
+# Core-Group
+
+## Zusammensetzung der Core-Group
+Die Core-Group besteht aus Personen, die sich für die Entwicklung und Pflege der offiziellen Release-Version von Stud.IP engagieren
+und dabei das Vertrauen der Core-Group besitzen. Es gibt keine Größenbeschränkung und keine Proporzregeln für die Core-Group.
+
+## Aufnahme in die Core-Group
+Potenzielle neue Mitglieder müssen von einem Mitglied der Core-Group in Form einer Aufnahmeabstimmung zur Aufnahme vorgeschlagen werden. Ein Aufnahmevorschlag gilt als akzeptiert, wenn mindestens 2/3 der Core-Group-Mitglieder zugestimmt haben. Das neu gewählte Core-Group-Mitglied muss der Aufnahme zustimmen.
+
+*Die Abstimmung ist anonym.*
+
+Eine Person soll regelmäßig dann zur Aufnahme in die Core-Group vorgeschlagen werden, wenn zu erwarten ist, dass sie längerfristiges persönliches Engagement für die Ziele der Core-Group zeigt. Die Core-Group-Mitglieder sollen bei der Abstimmung positiv berücksichtigen, ob ein potenzielles neues Mitglied einem noch nicht repräsentierten aktiv entwickelnden Standort entstammt. Die in der Person liegenden Gründe für oder gegen eine Aufnahme müssen aber ausschlaggebend sein.
+
+## Aufgaben der Core-Group-Mitglieder
+Die Core-Group definiert regelmäßig die aus ihrer Sicht notwendigen Aufgaben für die Entwicklung und Pflege der offiziellen Release-Version von Stud.IP. Beide Begriffe umfassen nicht ausschließlich technische Aspekte.
+
+Jede dieser Aufgaben soll von mindestens einem Core-Group-Mitglied verantwortlich übernommen werden.
+
+Jedes Core-Group-Mitglied soll mindestens eine dieser Aufgaben verantwortlich oder mitverantwortlich übernehmen. Das Mitglied kann Teilaufgaben auch an Nicht-Core-Group-Mitglieder übertragen, verantwortet die regelkonforme Ausführung aber gegenüber der gesamten Core-Group. Die Übertragung von Aufgaben geschieht durch die unwidersprochene Erklärung der Übernahme oder durch im Fall eines Widerspruchs durch Abstimmung mit 2/3-Mehrheit, die Abgabe oder Entziehung von Aufgaben durch Erklärung der Abgabe oder Abstimmung mit 2/3-Mehrheit.
+
+Jedes Core-Group-Mitglied soll sich an organisatorischen Tätigkeiten und Treffen der Core-Group beteiligen.
+
+Alle Core-Group-Mitglieder sollen sich an den Abstimmungen der Core-Group beteiligen.
+
+## Austritt und Ausschluss aus der Core-Group
+Die Core-Group-Mitgliedschaft endet mit der Erklärung des Austritts oder einer erfolgreichen Ausschluss-Abstimmung mit 2/3-Mehrheit. Jedes Core-Group-Mitglied kann den Ausschluss eines Mitgliedes durch Start einer entsprechenden Abstimmung beantragen.
+
+Ein Ausschlussantrag soll regelmäßig dann gestellt werden, wenn ein Mitglied den in 3. genannten Aufgaben ohne hinreichende Begründung nicht nachkommt.
+
+Die Abstimmung ist anonym.
+
+## Wiederaufnahme
+Die Wiederaufnahme in die Core-Group ist möglich.
+
+## Änderungen der Regeln
+Jedes Core-Group-Mitglied kann Änderungsvorschläge für diese Regeln einbringen.
+Ein Änderungsvorschlag gilt als akzeptiert, wenn 2/3 der Mitglieder zustimmen.
+
+# StEPs
+
+![image](../assets/dc7253456d69ace6eb97258059ce0a87/image.png)
+
+Stud.IP-Enhancement-Proposals (StEPs) sind Änderungsvorschläge für eine kommende Stud.IP-Release-Version. Sie sind das Standardinstrument zur Diskussion und strukturierten Umsetzung aller Änderungen, die nicht der Fehlerbehebung (BIESTs), der Einführung kleiner unstrittiger Änderungen (TICs) oder der Etablierung längerfristiger Codeänderungen (Lifters) dienen.
+
+Jeder Entwicklungsinteressierte darf Proposals einbringen. Proposals müssen konkrete Maßnahmen mit Umsätzungsplan vorschlagen und nicht lediglich ein Problem oder einen Wunsch identifizieren und um Lösungsvorschläge bitten. In diesen Fällen sind weiterhin die etablierten Foren (Developer-Board etc.) zu nutzen.
+
+Die Core-Group entscheidet über die Annahme eines StEPs.
+
+## Proposal
+Ein StEP muss folgendes beinhalten
+
+### Ziel
+Sehr knappe Zusammenfassung des Proposals (Titel/Überschrift)
+
+### Beschreibung:
+* Problembeschreibung bzw. Ziel der Änderung
+* Begründung, warum das Proposal sinnvoll bzw. notwendig erscheint
+
+### Maßnahmen (Überblick)
+* Knappe Beschreibung einer Lösung ohne Implementationsdetails
+* sollte dennoch in Grundzügen auf die wichtige Punkte eingehen (neue DB-Tabellen, Benutzung von vorhandenen Funktionalitäten,...).
+
+### Maßnahmen (Details)
+* konkrete Beschreibung, die keine wesentlichen Fragen hinsichtlich technischer Umsetzung und Interfacegestaltung offen lässt.
+* kann schrittweise entwickelt werden
+
+### Kurzbezeichnung des Integrationsaufwandes
+* gering: eigenständiges Modul, das über Konfigurationsschalter komplett ein-/ausschaltbar ist oder Änderungen an einzelnen Dateien, die keine Änderungen an Datenbank oder systemweit genutzten Datenstrukturen erfordern
+* mittel: tief greifende Änderung an wenigen Dateien, die geringe Modifikationen der Datenbankstruktur erfordern und keine systemweiten Auswirkungen haben, oder geringfügige Änderungen an vielen Dateien, die gut überschaubare systemweite Auswirkungen haben
+* hoch: alle Änderungen mit gravierenden systemweiten Auswirkungen
+
+### Durchführung
+* Verbindliches Angebot eines Durchführungsplanes, bestehend aus:
+* klar benannter Zuständigkeit für die Implementation, ggf. auch die Pflege
+* mit dem Releasezyklus abgestimmter Zeitplan für die Implementation
+
+## Kommentar- und Diskussionphase
+Die Mitglieder der Core-Group, aber auch alle anderen Entwicklungsinteressierten sind unmittelbar nach Veröffentlichung eines Proposals aufgerufen, es zu kommentieren und zu diskutieren. Der StEP-Text darf bis zur Abstimmung beliebig häufig geändert werden
+
+## Abstimmungsphase
+Nach Abschluss der Kommentar- und Diskussionphase startet ein Core-Group-Mitglied in der Veranstaltung "Stud.IP Enhancement Proposals" eine Abstimmung über Annahme oder Ablehnung eines Proposals in der dann aktuellen Form. Der StEP-Autor und die im Umsetzungsplan Genannten müssen ihre Zustimmung dazu signalisieren. Eine Abstimmung sollte nur dann erfolgen, wenn der Diskussionsverlauf eine Annahme als wahrscheinlich erscheinen lässt.
+
+Die Abstimmung wird mittels eines nichtanonymen Stud.IP-Votings in der Veranstaltung "Stud.IP Enhancement Proposals" vorgenommen, bei dem jedes Mitglied der Core-Group eine Stimme hat. Als Abstimmungszeitraum sind mindestens 14 Tage vorgesehen. Ein positives Abstimmungsergebnis gilt für 12 Monate.
+
+Für jedes Proposal sind folgende Abstimmungsmöglichkeiten vorzusehen:
+* Annahme (ja)
+* Ablehnung (nein)
+* Enthaltung
+
+Dabei gelten folgende Regeln:
+* Für die Annahme eines StEPs müssen mindestens doppelt soviele Ja- wie Nein-Stimmen abgegeben werden
+* Für eine gültige Abstimmung ist eine Mindestbeteiligung von 2/3 der Coregroup-Mitgliedern notwendig
+* Sobald eine absolute 2/3-Mehrheit der Coregroup-Mitglieder mit "Ja" gestimmt hat, kann das Voting auch schon vor Ablauf der zwei Wochen beendet werden
+
+Grundlage für das Abstimmungsverhalten sollten folgende Fragen sein:
+* ist das Feature sinnvoll?
+* ist das Feature mit der Stud.IP-Philosophie vereinbar?
+* ist die inhaltliche Konzeption akzeptabel und hinreichend generisch?
+* ist die technische Konzeption akzeptabel?
+* sind eventuelle Auswirkungen auf andere Stud.IP-Bereiche berücksichtigt?
+* sind alle offenen Fragen beantwortet?
+
+## Umsetzungsphase
+Nach der Annahme eines StEPs dürfen die im Proposal genannten Zuständigen innerhalb von 12 Monaten eine dem StEP-Text entsprechende Implementation in einen für diesen StEP angelegten branch des Stud.IP-gitlab einchecken bzw. einchecken lassen. Eine Verlängerung der Frist ist nach entsprechender Core-Group-Abstimmung möglich.
+
+Bis zum Codefreeze muss ein öffentlicher branch der Umsetzung vorliegen sowie ein MR (ohne Draft) und die entsprechenden QM::?-Labels an dem Issue müssen gesetzt sein.
+
+## Test- und Vetophase
+Die Core-Group definiert regelmäßig eine Liste von Zuständigkeiten mit Vetorecht, die die Qualität des Stud.IP-Releases sichern sollen. Die jeweils aktuelle Liste der Zuständigkeiten wird zusammen mit diesen StEP-Regeln veröffentlicht und umfasst u.a. Aspekte wie Einhaltung der Code-Konventionen, Code-Qualität, Einhaltung der GUI-Richtlinien und hinreichende Dokumenation. Das Vetorecht darf (gemeinschaftlich) von den jeweils zuständigen Core-Group-Mitglieder vom ersten Check-In des StEPs bis zum Ende des Testzeitraumes ausgeübt werden.
+
+Erst wenn alle Qualitätsbeauftragten ihre Zustimmung zur Umsetzung des StEPs in Form eines "+" abgegeben haben darf der StEP in den main überführt werden. Verantwortliche Core-Group-Mitglieder sind verpflichtet, möglichst frühzeitig nach dem Einchecken eines angenommenen StEPs Rückmeldung zu kritischen Punkten zu geben und konstruktive Vorschläge zur Behebung erkannter Missstände zu machen. Ein endgültiges Veto ("-") erfolgt, wenn diese Vorschläge nicht beachtet werden und der Zeitplan für das Release keine andere Wahl lässt. StEPs sollten möglichst in der Reihenfolge getestet und in den trunk überführt werden, in der sie von den Entwicklern zum Testen freigegeben wurden.
+
+## Release-Branch
+Zu einem am Anfang des Release-Zyklus festgelegten Termin erfolgt das Ausbranchen des neuen Releases aus dem main.
+
+## Status
+Jeder StEP befindet sich in genau einem Status, der jeweils in der Beschreibung aktualisiert wird:
+
+* **neu** - initialer Status, der StEP wurde vorgestellt und befindet sich in der Diskussions- oder 1. Abstimmungsphase
+* **angenommen** - die erste Abstimmung ist positiv verlaufen, der StEP darf in den trunk eingespielt werden
+* **test** - der Code wurde in einen eigenen branch eingebaut und ist nach Meinung des Verantwortlichen vollständig und weitgehend fehlerfrei; allgemeine Aufforderung zum Testen an alle
+* **veto** - gegen den eingespielten StEP hat ein zuständiges Core-Group-Mitglied Einwände erhoben, der StEP kann in dieser Form nicht in den trunk gelangen
+* **tested** - alle Qualitäts-Zuständigen haben ihr Einverständnis gegeben, der StEP darf in den trunk übernommen werden
+* **release** - es wurde endgültig kein Veto erhoben und der StEP ist in den Release-Branch eingegangen
+* **abgelehnt** - die erste Abstimmung ist negativ verlaufen oder liegt länger als 12 Monate zurück, oder ein Veto hat verhindert, dass der StEP in das Release gelangt; das Verfahren muss neu gestartet werden oder der StEP wird verworfen
+* **verworfen** - die Funktionalität wird nicht mehr benötigt, ist durch anderen StEP abgedeckt, etc.
+
+![image](../assets/dc7253456d69ace6eb97258059ce0a87/image.png)
+
+# TICs
+
+Nicht jede Änderung muss den StEP-Prozess durchlaufen. TICs (tiny improvement commits) sind eingecheckte Code-Änderungen, die keiner Abstimmung bedürfen.
+
+## Erstellen
+
+Ein TIC wird durch Anlegen eines Tickets vom Typ TIC im Gitlab erzeugt. Alle Entwickler mit Schreibrechten im svn-Repository für den Release-Zweig dürfen TICs anlegen. Das Ticket muss eine Beschreibung der Änderungen enthalten.
+
+## Einbauen
+
+Alle TIC-bezüglichen Commits müssen dem Ticket zugeordnet werden. Commits sind nur bis zum Codefreeze erlaubt. Bis zum Codefreeze muss ein öffentlicher branch der Umsetzung vorliegen sowie ein MR (ohne Draft).
+
+## Widerspruch
+
+Sämtlicher einem TIC zugeordneten Commits müssen rückgängig gemacht werden, wenn ein Core-Group-Mitgliedes Widerspruch gegen den TIC einlegt. Die Widerspruchsfrist endet mit dem Release-Branch.
+
+TICs, gegen die Widerspruch eingelegt wurde, können in StEPs umgewandelt und dann diskutiert werden.
+
+# Lifters
+
+* **L**aufende, **i**nkrementell **f**ortschreitende **Te**chnik**r**enovierung für **S**tud.IP*
+
+Einige wichtige, sinnvolle und gewünschte Überarbeitungen des Stud.IP-Quellcodes erfordern Eingriffe an vielen Stellen des Codes. Beschränkte Ressourcen bei Entwicklungskapazität und Qualitätssicherung machen aber eine Umstellung "auf einen Schlag" unmöglich. Insbesondere will es die Entwickler-Community vermeiden, laufende Entwicklungen bis zur Fertigstellung einer runderneuerten Version des Codes zu stoppen. Verzögerungen bei der - häufig rein technisch motivierten und nach außen "unichtbaren" - Runderneuerung könnten das gesamte Projekt gefährden.
+
+Die "Stud.IP-Lifters" etablieren ein Verfahren, solche Überarbeitungen in die laufenden Release-Zyklen einzubinden. Sie setzen klare Ziele und definieren eindeutige Anweisungen für Entwickler, wie sie bestehenden Code überarbeiten und neuen Code Lifter-konform gestalten können.
+
+"Lifters" definieren Mittel zur Fortschrittskontrolle. Zwar haben sie keinen verbindlichen Endtermin oder ein verbindliches Fertigstellungs-Release, es ist aber zu jedem Zeitpunkt nachvollziehbar, wie weit die Umsellungsarbeiten fortgeschritten sind.
+
+"Lifters" helfen den Entwicklern. Sie geben klare Richtlinien, wie bislang oft individuell zu lösende Codierungs-Probleme zu handhaben sind. Sie geben Einsteigern und Entwicklern, die dem Projekt "etwas Gutes" tun wollen, ohne genau zu wissen, welche Arbeiten gerade sinnvoll und erwünscht sind, wohlportionierte Arbeitspakete an die Hand.
+
+"Lifters" laufen quasi "nebenher" zur normalen Stud.IP-Entwicklung. sie sollen klar und einfach beschrieben, wie Code-Überarbeitungen, die "ohnehin" passieren, gleich weitere Aspekte mitberücksichtigen können. "Lifters" sind damit keine neue Art von Vorschrift, sondern vereinheitlichen bisherige Entwicklungsleitlinien.
+
+"Lifters" durchlaufen einen ähnlichen Prozess wie StEPs. Sie werden von einem Core-Group-Mitglied vorgeschlagen, öffentlich diskutiert und schließlich in der Core-Group verbindlich abgestimmt. Nach der ersten Abstimung hat ein Lifter den Status "in der Umsetzung". SVN-Checkins, die Lifter-Richtlinien umetzen, bedürfen keines gesonderten StEPs (Stichwort: Kein Check-In ohne StEP), müssen aber im Rahmen der Fortschrittskontrolle (s.u.) bekannt gemacht und getestet werden.
+
+Damit haben Lifter folgende Stadien:
+* **neu** = Vorschlag in der Diskussion
+* **angenommen** = Lifter zur Umsetzung verabschiedet. Angenommene Lifter sind verbindlich für alle neuen Entwicklungen, ausgenommen Bugfixes.
+* **abgeschlossen** = Alle Arbeiten sind abgeschlossen. Abgeschlossene Lifter sind verbindlich für alle zukünftigen Entwicklungen.
+* **inaktiv** = Alter Lifter, der nicht mehr beachtet werden muss.
+
+## Formulieren
+Lifter werden über das Formular im Wiki der Veranstaltung "Stud.IP Lifters" erstellt und anchließend im Wiki gepflegt.
+Dabei wird ein Ticket im Gitlab angelegt.
+Alle folgenden Lifter-bezogenen Checkins müssen sich auf dieses Ticket beziehen.
+
+Lifter-Vorschläge müssen folgende Anforderungen erfüllen:
+* eindeutig spezifizierte Umstellungsregeln (umfassende Beschreibung von Vorher- und Nachher-Zustand)
+* alter Code muss in der Übergangssphase lauffähig bleiben
+* soweit automatisierte Umstellungshilfen erstellt werden können (z.B. sed-Skripte), solten diese vom Lifter-Autor erstellt werden und ihre Anwendung wie manuelle Umstellungen dokumentiert werden. Auch automatische Umstellungen müssen wie manuelle kontrolliert werden.
+* Im Rahmen der Diskussion sollen Testszenarien, d.h. belastbare Aussagen über den Zielzustand definiert werden, die laufend qualitätssichernd, vor allem vor Release-Terminen, durchgeführt werden können. Ausreichende Testszenarien müssen VOR der Core-Group-Abstimmung vorgelegt werden.
+* Fortschrittskontrolle. Ein Lifter-Vorschlag muss eine abarbeitbare Liste von Arbeitspaketen enthalten, die zur Erledigung des Lifters notwendig sind. Dies kann z.B. durch Markierung der zu bearbeitenden Dateien (s.u.) passieren.
+* Ergeben sich im Laufe der Arbeiten an einem Lifter neuen Anforderungen, die von der ursprünglichen Beschreibung abweichen, muss entweder:
+ * für die fragliche Anpassung ein StEP erstellt werden,
+ * die Lifter-Beschreibung ergänzt und die Ergänzung zur Abstimmung gestellt werden, oder,
+ * der Lifter per Abstimmung (s.u.) eingestellt werden.
+* Lifter können mit der gleichen Mehrheit, die zu ihrer Annahme nötig war, als inaktiv oder bloße Empfehlung gekennzeichnet werden. Sollten die schon vorhandenen Änderungen rückgängig gemacht werden, ist dies in offensichtlich einfachen Fällen unmittelbar nach der Abstimmung zulässig, in komplexeren Fällen nur nach Annahme eines entsprechenden StEPs oder Lifters.
+* Neu erstelle Dateien und Arbeiten an StEPs müssen nach Annahme eines Lifters dessen Anforderungen genügen.
+* Bugfixes müssen sich bei Dateien, die noch nicht Lifter-konform sind, nicht an die Lifter-Anforderungen halten.
+
+## Abstimmung
+Die Abstimmung wird mittels eines nichtanonymen Stud.IP-Votings in der Core-Group-Veranstaltung vorgenommen, bei dem jedes Mitglied der Core-Group eine Stimme hat. Als Abstimmungszeitraum sind mindestens 14 Tage vorgesehen.
+
+Für jedes Proposal sind folgende Abstimmungsmöglichkeiten vorzusehen:
+* Annahme (ja)
+* Ablehnung (nein)
+* Enthaltung
+
+Dabei gelten folgende Regeln:
+* Für die Annahme eines Lifters müssen mindestens doppelt soviele Ja- wie Nein-Stimmen abgegeben werden
+* Eine Enthaltung zählt unabhängig vom Komplexitätsgrad immer als Enthaltung
+* Für eine gültige Abstimmung ist eine Mindestbeteiligung von 2/3 der Coregroup-Mitgliedern notwendig
+* Sobald eine absolute 2/3-Mehrheit der Coregroup-Mitglieder mit "Ja" gestimmt hat, kann das Voting auch schon vor Ablauf der zwei Wochen beendet werden
+
+Grundlage für das eigene Abstimmungsverhalten sollten folgende Fragen sein:
+* ist das Feature sinnvoll?
+* ist das Feature mit der Stud.IP-Philosophie vereinbar?
+* ist die inhaltliche Konzeption akzeptabel und hinreichend generisch?
+* ist die technische Konzeption akzeptabel?
+* sind eventuelle Auswirkungen auf andere Stud.IP-Bereiche berücksichtigt?
+* sind alle offenen Fragen beantwortet?
+
+### Umsetzungsphase und Fortschrittskontrolle
+In aller Regel betreffen Lifters PHP-Dateien. In diesen Fällen ist nach Annahme eines Lifters ein SVN-Checkin durchzuführen, der in allen betroffenen Dateien einen Kommentar der Form
+```php
+<?
+# Lifter001: TODO - evtl. genauere Beschreibung.. (alles nach dem - ist Beschreibung)
+?>
+```
+einfügt.
+
+Ist ein Lifter für eine Datei komplett oder teilweise erledigt, ist der Kommentar zu ändern in:
+
+```php
+<?
+# Lifter001: TEST - evtl. genauere Beschreibung der Maßnahmen.. (alles nach dem - ist Beschreibung)
+?>
+```
+
+Bei teilweiser Erledigung kann zusätzlich erläutert werden, welche Arbeiten noch verbleiben.
+
+In HTML- oder CSS-Dateien werden ebenfalls den obigen Konventionen entsprechende Kommentare angebracht.
+
+Bei zu bearbeitenden Binärdateien ist eine Markierung in der Datei nicht möglich. Stattdessen ist in der Lifter-Beschreibung eine Liste von noch anzupassenden Dateien zu pflegen, die jeweils aktualisiert wird. Da davon auszugehen ist, dass solche Typen seltener sind, werden genauere Regelungen erst bei Bedarf getroffen.
+
+## Qualitätskontrolle
+Lifter erfordern erhöhte Aufmerksamkeit beim Testen. In kritischen Phasen eines Branches (Betatestphase im Release-Branch) dürfen dort keine Lifter-bezogenen Änderungen eingecheckt werden. Bei größeren Checkins bzw. besonders kritischen Codeteilen verpflichtet sich die Core-Group angemessene Tests durchzuführen.
+
+Jedes Lifter-bezogene Checkin wird zunächst mit dem Status "TEST" versehen (s.o.). Ein anderer Entwickler mit Schreibrechten im SVN muss den Code überprüfen und testen und anschließend die TEST-Markierung entfernen und den Code neu einchecken. Damit erscheint er als Tester im Revisions-Log. Wurde eine Datei nur teilweise bearbeitet, setzt der Tester wieder eine TODO-Markierung ein und beschreibt ggf. die noch verbleibenden Maßnahmen.
+
+LIFTERN ist nur erlaubt bis zur TIC-Deadline und unterliegt dann demselben Test- und Vetoverfahren wie StEPs und TICs.
+
+Entdeckte Fehler (die vermutlich mit einem Lifter zusammenhängen) werden als ganz normale BIESTs über das PlugIn in der BIEST-Veranstaltung oder direkt im Gitlab dokumentiert.
+
+## Abschluss und Dokumentation
+Insbesondere für die Außendarstellung ist es wichtig, Lifter als einmalige Anstrengung als abgeschlossen zu erklären: In der Regel wird damit ein positiv vermarktbarer Zustand erreicht (Multi-Tab-Browsing geht jetzt, Stud.IP basiert vollständig auf Layout-Templates, ...).
+
+Um einen Lifter als abgeschlossen zu erklären ist eine Abstimmung der Core-Group erforderlich, die den gleichen Bedingungen wie zur Annahme unterliegt. Damit ist sichergestellt, dass sich die Core-Group fortan mit dem erreichten Status identifiziert und öffentliche Aussagen über den erreichten Status mitträgt.
+
+Ein abgeschlossener Lifter bekommt den Status "done" und ist weiterhin verbindlich für alle Neuentwicklungen, Erweiterungen und Bugfixes. Werden anschließend Stellen identifiziert, die nicht Lifter-konform sind, sind sie als normale Biests zu melden.
+
+Verworfene oder obsolet gewordene Lifter werden mit dem Status "inaktiv" markiert und sind nicht verbindlich für die weitere Entwicklung.
+
+# BIESTs
+
+## Fehler melden
+
+BIESTer werden über das gitlab des Stud.IP-Projektes gemeldet und mit den entsprechenden Labels "BIEST" und "Version::x.x" für die früheste Version, in der der Fehler aufgetreten ist ausgezeichnet. Optional kann noch die betroffene Komponente angegeben werden.
+
+## Fehler beheben
+
+Checkins, die ein BIEST beheben, müssen dem jeweiligen Ticket zugeordnet werden.
+
+Komplexe Fehler sollten in einem eigenen branch behandelt werden und die Fixes wenn möglich von einer zweiten Person approved werden.
+
+Bugfixes müssen immer in einem einzigen Commit auf den main gebracht werden. Kommt es zu einem Fehler und das Issue ist noch nicht gemerged, muss der Commit rückgängig gemacht und der Bug in einem MR gelöst werden. Ist das Issue bereits in alte Versionen portiert, so muss ein neues Issue aufgemacht werden, was dieses Problem behebt.
+
+# Release
+
+## Service-Release erstellen
+
+Die folgenden Schritte sind zum Erstellen eines Service-Releases (z.B. 4.5.5) durchzuführen:
+* Klären ob alle relevanten Bugfixes "nach unten" portiert wurden (nachfragen bei André oder Jan-Hendrik)
+* Prüfen ob alle Issues & Merge requests am entsprechenden Milestone geschlossen wurden, gegebenenfalls offene Issues an nächsten Milestone verschieben
+* Auschecken des aktuellen Standes des Release-Branches
+* Kompilieren der notwendigen Assets
+* Test der wichtigsten Stud.IP-Seiten
+* gegebenenfalls Übernahme der Changelog-Einträge vorhergehender Service-Releases (z.B. von 4.5.5 -> 4.6.3)
+* Extrahieren aller Issues des Milestones, umformatieren und oben zum Changelog hinzufügen
+* Link zu den Issues des Milestones im gitlab oben zum Changelog hinzufügen
+* aktuelles Datum und version number oben zum Changelog hinzufügen
+* version number in ./VERSION und ./lib/bootstrap.php aktualisieren
+* aktuellen Stand des Release-Branches mit der version number taggen
+* Milestone schließen
+* Ankündigung auf dem Developer-Server erstellen bzw. aktualisieren
+
+## Main-Release erstellen
+
+Beim Erstellen eines Main-Releases (z.B. 4.6) sind zusätzlich zu der Liste oben die folgenden Schritte notwendig:
+* Erstellen des entsprechenden Branches
+* Vervollständigung der Übersetzung und Aktualisieren der Übersetztungsdateien
+* history.txt Aktualisieren
+* gegebenenfalls Aktualisierung von ./AUTHORS ./INSTALL ./README
+* Umstellen von DEFAULT_ENV = 'production' in ./lib/bootstrap.php
+* Test einer Neuinstallation
+* Tests eines Upgrades von der letzten nicht mehr unterstützten Stud.IP-Version aus
+* gegebenenfalls Anpassung von https://www.studip.de/home/download/
diff --git a/docs/docs/start.md b/docs/docs/start.md
new file mode 100644
index 0000000..4e5683e
--- /dev/null
+++ b/docs/docs/start.md
@@ -0,0 +1,5 @@
+---
+id: start
+title: Entwicklungsdokumentation
+sidebar_label: Übersicht
+---
diff --git a/docs/docs/testing/codeception.md b/docs/docs/testing/codeception.md
new file mode 100644
index 0000000..ee6c191
--- /dev/null
+++ b/docs/docs/testing/codeception.md
@@ -0,0 +1,238 @@
+---
+title: Codeception (PHP)
+---
+
+# Welches Tool verwenden wir?
+
+Um Stud.IP mit Tests zu versehen verwenden wir seit der Version 3.2 die Testsuite Codeception, siehe http://codeception.com/.
+
+Codeception beinhaltet PHPUnit für Unit- und API-Tests und ermöglicht das Schreiben von Acceptance-Tests ebenfalls in PHP. Die Acceptance-Tests können dann mit einem internen PHPBrowser ausgeführt werden oder auch durch einfaches umschalten in der Konfiguration mit einem Selenium-Server (http://www.seleniumhq.org/).
+
+Um Codeception zu installieren, verwenden wir Composer. Dabei handelt es sich um einen Dependency-Manager, der einem dabei hilft Dinge wie Codeception zu installiern und auf dem aktuellsten Stand zu halten, siehe https://getcomposer.org/.
+
+PHPUnit ist ein Testframework für PHP, es bietet weitreichende Möglichkeiten für den Unit-Test, die Arbeitsweise ist der offiziellen Dokumentation unter https://phpunit.de/documentation.html zu entnehmen.
+
+## Wie installiere ich composer
+
+Auf den Seite von composer wird gut erklärt, wie man diesen installiert: https://getcomposer.org/doc/00-intro.md.
+
+Befindet man sich unter Linux, so geht das im einfachsten Fall mit folgendem Befehl:
+
+```shell
+curl -sS https://getcomposer.org/installer | php
+```
+
+Dabei wird in das aktuelle Verzeichnis eine composer.phar gelegt, die man dann dann wie ein Kommandozeilentool verwenden kann. Genau das werden dann im nächsten Schritt tun.
+
+## Wie installiere ich codeception
+
+Nachdem man composer installiert hat, kann man codeception ganz bequem über folgenden Befehl installieren:
+```shell
+php composer.phar install
+```
+
+oder, falls man composer in seinem Suchpfad hinterlegt hat (z.B. durch Installation via eines Paketmanagers):
+```shell
+composer install
+```
+
+Danach wird im Stud.IP-Hauptverzeichnis ein Verzeichnis namens "composer" angelegt worin alle nötigen Dateien hinterlegt sind.
+
+# Wie führe ich die Tests aus?
+
+Standardmäßig werden nur die Unit-Tests ausgeführt, da man für Acceptance- und API-Tests noch ein wenig seine Testumgebung konfigurieren muss, bevor die sinnvoll durchführbar sind.
+
+Um die Unit-Tests auszuführen (s.u.) reicht folgender Befehl
+```shell
+make test
+```
+
+Das Makefile definiert dabei einen Fallback auf PHPUnit, welches verwendet wird, wenn keine Codeception-Installation gefunden wurde.
+
+Dadurch werden alle Unit-Test mit PHPUnit durchgeführt. Die Ausgabe sieht dann ungefähr so aus (bei erfolgreichem durchlaufen der Tests):
+
+![image](../assets/1afde9c989ac66fc23192c4e7ca6aea8/image.png)
+
+# Mit Codeception arbeiten
+
+Codeception besitzt ein Kommandozeilen-Tool mit dem man die nötigen Operationen durchführen kann. Die genaue Dokumentation der Arbeitsweise findet man auf den Seiten von Codeception unter http://codeception.com/. Die im folgenden typischen Anwendungsfälle sollen dabei nur ergänzend als Einstiegshilfe sein.
+
+## Verzeichnisstruktur
+
+Codeception arbeitet im Verzeichnis *tests*. Dort liegen die einzelnen Testsuiten in den entsprechenden Untervezeichnissen, also *acceptance*, *unit* usw. Es gibt für jede Testsuite eine eigene Konfigurationsdatei die auf *.suite.yml* endet. Dort hinterlegt sind die Einstellungen für die entsprechende Testsuite.
+
+## Tests ausführen, the educated way
+
+Wie weiter oben beschrieben reicht normalerweise `make test` um die Unit-Tests auszuführen. Was dabei tatsächlich passiert ist der Aufruf des Befehls
+```shell
+./composer/bin/codecept run unit
+```
+
+Es wird dabei nur eine bestimmte Testsuite ausgeführt (siehe zweiter Parameter, "unit"). Möchte man alle Testsuiten ausführen, so verwendet man folgenden Befehl:
+```shell
+./composer/bin/codecept run
+```
+
+Dieser wird allerdings fehlschlagen, wenn die anderen Testsuiten noch nicht fertig konfiguriert wurden. Wie das geht steht in den folgenden Abschnitten.
+
+Es ist auch möglich nur einen bestimmten TestCase zu berücksichtigen. Möchte man zum Beispiel lediglich den StudipFileloaderTest durchführen, ruft man codeception wie folgt auf:
+
+```shell
+./composer/bin/codecept run unit lib/classes/StudipFileloaderTest.php
+```
+
+Das hilft einem beim Schreiben neuer Tests - man muss nicht alle momentan irrelevanten Unit-Tests mit durchlaufen lassen um zu sehen ob der neu gebaute Test funktioniert.
+
+## Acceptance-Tests
+
+Acceptance-Test sind Test die auf oberster Ebene arbeiten und einen Stud.IP-Nutzer simulieren. Sie werden verwendet um Funktionalität aus Nutzersicht sicherzustellen und reichen dabei von einfachen, allgemeinen Tests wie "Kann sich ein Nutzer einloggen" bis zu sehr spezifischen Anforderungen wie "Kann ein Nutzer mit der Rechtestufe autor in einer offenen Veranstaltung einen Forums-Eintrag innerhalb eine Bereiches schreiben".
+
+Die offizielle Dokumentation für Acceptance-Tests befindet sich hier: http://codeception.com/docs/03-AcceptanceTests
+
+Codeception bietet PHP-Funktionen an, um Acceptance-Tests zu schreiben. Diese werden dann je nach Einstellung mit einem internen PHP-Browser ausgeführt oder in Selenium-Tests übersetzt und an einen Selenium-Server weitergereicht.
+
+### Testen mit PHPBrowser
+
+Um die vorhandenen Tests ausführen zu können, benötigt man eine lauffähige Version des Stud.IPs, sprich es muss über einen Webbrowser aufrufbar und verwendbar sein. Ist das erledigt, trägt man die URL zu diesem System in die Konfiguration der Acceptance-Tests ein. Diese befindet sich in folgender Datei:
+`tests/acceptance.suite.yml`
+
+Dort trägt man unter "url" den Pfad zum public-Verzeichnis der Stud.IP-Testinstallation ein:
+
+```json
+class_name: WebGuy
+modules:
+ enabled:
+ - PhpBrowser
+ - WebHelper
+ config:
+ PhpBrowser:
+ url: 'http://localhost/pfad/zu/studip/public/'
+```
+
+**Wichtig**: Nach jeder Änderung an der Konfiguration sollte
+man einmal den Befehl `./composer/bin/codecept build` ausführen um z.B. neu konfigurierte Module zur Verfügung zu haben.
+
+Nun kann man mittels
+```shell
+./composer/bin/codecept run acceptance
+```
+die Tests durchlaufen lassen. Üblicherweise sollte man die Datenbank vor jedem Testdurchlauf auf einen definierten Stand bringen.
+Wie das funktioniert, steht im Abschnitt "Testen mit Datenbank"
+
+
+### Testen mit Selenium
+
+Um die Tests mit Selenium ausführen zu lassen, benötigt man zuerst einen Selenium-Server. Dabei handelt es sich um ein Java-Programm, welches man auf http://www.seleniumhq.org/download/ im Abschnitt "Selenium Server" herunterladen und danach mit
+```shell
+java -jar selenium-server-standalone-VERSION..jar
+```
+starten kann.
+
+Ist das erledigt weißt man Codeception an, für die Acceptance-Tests nun den Selenium-Server zu verwenden. Dafür editiert man wiederum
+```shell
+tests/acceptance.suite.yml
+```
+und ergänzt um einen weiteren Eintrag und ändert die aktivierten Module, so dass die Konfiguration nun ungefähr wie folgt aussieht:
+
+```json
+class_name: WebGuy
+modules:
+ enabled:
+ - WebDriver
+ - WebHelper
+ config:
+ PhpBrowser:
+ url: 'http://localhost/pfad/zu/studip/public/'
+ WebDriver:
+ url: 'http://localhost/pfad/zu/studip/public/'
+ browser: 'firefox'
+```
+
+Bei "browser" kann der verwendete Browser definiert werden. Es ist auch möglich, dieselben Tests in verschiedenen Browser laufen zu lassen, das wird gut im Abschnitt "Environment" auf http://codeception.com/docs/07-AdvancedUsage#Environments erklärt.
+
+**Wichtig**: Nach jeder Änderung an der Konfiguration sollte man einmal den Befehl `./composer/bin/codecept build` ausführen um z.B. neu konfigurierte Module zur Verfügung zu haben.
+
+## REST-API-Tests
+
+Möchte man Tests für Webservices schreiben, so findet man gute Dokumentation dafür unter
+http://codeception.com/docs/10-WebServices
+
+Grob zusammengefasst muss man eine Testsuite generieren (lassen), z.B. mit dem Namen api, dort platziert man dann seine Tests. Man hat dort dann die Möglichkeit HTTP-Requests (GET, POST, PUT, DELETE, etc.) abzusetzen und das Ergebnis zu überprüfen. Zusätzlich dazu macht es Sinn, die Datenbank vorher immer auf einen definierten Stand setzen zu lassen (siehe den Abschnitt "Testen mit Datenbank")
+
+# Testen mit Datenbank
+
+Insbesondere bei Acceptance-Tests ist es sinnvoll, die Datenbank vor dem Testen auf einen definierten Zustand zu setzen. Das geht Dank Codeception sehr bequem.
+Man legt dafür lediglich einen Datenbankdump in das Verzeichnis *tests/_data* und konfiguriert die gewünschte Testsuite.
+Möchte man also bei den AcceptanceTest die Datenbank auf einen definierten Zustand setzen, editiert man die Datei
+`tests/acceptance.suite.yml`
+und ergänzt die Konfiguration für die Datenbank.
+
+Das sieht dann ungefähr so aus:
+```json
+class_name: WebGuy
+modules:
+ enabled:
+ - WebDriver
+ - WebHelper
+ - Db
+ config:
+ PhpBrowser:
+ url: 'http://localhost/pfad/zu/studip/public/'
+ WebDriver:
+ url: 'http://localhost/pfad/zu/studip/public/'
+ browser: 'firefox'
+ Db:
+ dsn: 'mysql:host=localhost;dbname=studip'
+ user: 'root'
+ password: 'passwort'
+ dump: tests/_data/studip_acceptance_test.sql
+```
+
+Man hat nun darüber hinaus auch die Möglichkeit direkt bei den Tests Datenbankinhalte zu überprüfen. Ausführliche Dokumentation findet sich auch hier wieder bei Codeception, siehe http://codeception.com/docs/modules/Db.
+
+
+# Testen von SORM-Klassen
+
+Zum Testen von SORM-Klassen ist es nicht notwendig, eine externe Datenquelle einzurichten. Es können direkt in der Codeception-Testklasse SORM-Objekte erzeugt werden und für Tests verwendet werden. Hierzu müssen in die _before()- und _after()-Methode der Codeception-Testklasse ein paar Codezeilen eingefügt werden.
+
+In die _before()-Methode der Codeception-Testklasse wird Code eingefügt, welcher die folgenden Aktionen durchführt:
+
+* Erstellen der Stud.IP-Datenbankverbindung
+* Starten einer Datenbank-Transaktion
+* Setzen der Datenbankverbindung im DBManager
+* Gegebenenfalls Erzeugung von SORM-Objekten, welche für die Tests benötigt werden
+
+
+
+Der Code innerhalb der `_before()`-Methode der Testklasse kann z.B. folgendermaßen aussehen:
+
+```php
+//Erstellen der Stud.IP-Datenbankverbindung:
+$this->db_handle = new \StudipPDO(
+ 'mysql:host='
+ . $GLOBALS['DB_STUDIP_HOST']
+ . ';dbname='
+ . $GLOBALS['DB_STUDIP_DATABASE'],
+ $GLOBALS['DB_STUDIP_USER'],
+ $GLOBALS['DB_STUDIP_PASSWORD']
+);
+
+//Starten einer Datenbank-Transaktion:
+$this->db_handle->beginTransaction();
+
+//Setzen der Datenbankverbindung im DBManager:
+\DBManager::getInstance()->setConnection('studip', $this->db_handle);
+
+//Falls notwendig: Erzeugen von SORM-Objekten:
+$u = new User();
+$u->username = 'sorm_test_user';
+[...]
+$u->store();
+```
+
+
+In der _after()-Methode der Codeception-Testklasse wird nur die Datenbank-Transaktion zurückgesetzt (ein "Rollback" durchgeführt), um die Änderungen an der Datenbank, welche innerhalb der Tests durchgeführt wurden, rückgängig zu machen. Die _after()-Methode enthält also folgende Codezeile:
+
+```php
+$this->db_handle->rollBack();
+```
diff --git a/docs/docs/testing/e2e.md b/docs/docs/testing/e2e.md
new file mode 100644
index 0000000..bb58be2
--- /dev/null
+++ b/docs/docs/testing/e2e.md
@@ -0,0 +1,54 @@
+---
+title: End-to-end Tests (Playwright)
+---
+
+# Welches Tool verwenden wir?
+
+Um Stud.IP mit Tests zu versehen verwenden wir seit der Version 5.4
+das Werkzeug Playwright, siehe https://playwright.dev
+
+Playwright ist ein Werkzeug für End-to-End-Tests von Webanwendungen:
+- Unterstützt mehrere Browser (Chromium, Firefox, WebKit)
+- Führt Tests auf verschiedenen Betriebssystemen aus
+- Ermöglicht browserübergreifende Tests mit einer einheitlichen API
+- Wartet automatisch auf interaktionsbereite UI-Elemente
+- Führt Tests parallel aus
+- Kann Videos und Screenshots der Testausführung aufzeichnen
+
+Playwright ist schon als Abhängigkeit in `package.json` enthalten und
+wird über `npm install` installiert.
+
+## Wie konfiguriere ich Playwright?
+
+Um mit Playwright arbeiten zu können, benötigst du eine laufende
+Stud.IP-Installation, die folgende SQL-Dumps eingespielt hat:
+
+- `db/studip.sql`
+- `db/studip_default_data.sql`
+- `db/studip_demo_data.sql`
+- `db/studip_resources_default_data.sql`
+
+Dann müssen Umgebungsvariablen gesetzt werden:
+
+- `PLAYWRIGHT_BASE_URL`
+
+Das passiert am einfachsten in einer `.env`-Datei. Also zum Beispiel:
+
+```
+PLAYWRIGHT_BASE_URL="http://localhost:8080"
+```
+
+## Wie führe ich Playwright aus?
+
+Dazu kannst du auf der Kommandozeile folgenden Aufruf starten:
+
+```shell
+npx playwright test
+```
+
+Da aktuell die A11y-Tests fehlschlagen, kannst du auch die mit dem Tag
+`a11y` versehenen Tests überspringen:
+
+```shell
+npx playwright test -gv a11y
+```
diff --git a/docs/docs/thank-you.md b/docs/docs/thank-you.md
new file mode 100644
index 0000000..808847e
--- /dev/null
+++ b/docs/docs/thank-you.md
@@ -0,0 +1,17 @@
+---
+title: Thank you!
+---
+
+Congratulations on making it this far!
+
+You have learned the **basics of Docusaurus** and made some changes to the **initial template**.
+
+But Docusaurus has **much more to offer**!
+
+## What's next?
+
+- [Read the official documentation](https://v2.docusaurus.io/).
+- [Design and Layout your Docusaurus site](https://v2.docusaurus.io/docs/styling-layout)
+- [Integrate a search bar into your site](https://v2.docusaurus.io/docs/search)
+- [Find inspirations in Docusaurus showcase](https://v2.docusaurus.io/showcase)
+- [Get involved in the Docusaurus Community](https://v2.docusaurus.io/community/support)
diff --git a/docs/docs/visual-style-guide/css.md b/docs/docs/visual-style-guide/css.md
new file mode 100644
index 0000000..7dd6b35
--- /dev/null
+++ b/docs/docs/visual-style-guide/css.md
@@ -0,0 +1,134 @@
+---
+title: CSS
+sidebar_label: CSS
+---
+
+### Interaktionselemente
+Text muss noch aktualisiert werden
+
+* Was soll anklickbar sein?
+ * Navigationselemente
+ * Buttons mit Text
+ * Textlinks: Textlinks sollen nicht im Fließtext von Systembestandteilen (zB. in Onfoboxen) erscheinen, auch wenn dies in der Vergangenheit häufig so gemacht wurde. Textlinks erscheinen nur im Fließtext eines Feldess (Nutzereingabe oder Systemfeld) und werden durch die Formatierungsfunktionen in diesem Fall mit dem Link-Icon versehen. Nur in diesem Fall kann der Nutzer diesen Link leicht vom Fließtext unterscheiden.
+ * Icons: Grundsätzlich sollen Icons nur in der blauen Variante klickbar sein (die Farbe entspricht der normalen Linkfarbe), Ausnahmen gibt es derzeit auf der Seite "Meine Veranstaltungen" und bei einigen Objekticons. Die Ausnahmen sollen Ausnahmen bleiben, dh. neue klickbare Icons sind grundsätzlich in Blau zu halten. Für Aktionen wie Anlegen, löschen oder Verschieben gibt es Zusätze (in der Regel rot), die auf die besondere Funktion dieses Icons hinweisen. (siehe unter [Icons](Visual-Style-Guide#Icons))
+
+* Wann benutzt man zur Interaktion einen Textlink, wann einen Tab, wann einen Button mit Text und wann ein Icon?
+ * Tab: Umschalten zwischen verschiedenen Ansichten/Aspekten eines Objekts
+ * Textlink: Navigation zu einer anderen Seite, keine "Aktion" i.e.S.
+ * Icon: Auslösen einer Aktion
+ * Button: Auslösen einer Aktion
+ * Wann benutzt man ein Icon und wann einen Button?
+
+* Wie sollen Buttons benannt werden?
+ * Beispiel: "übernehmen" vs. "abschicken" vs. "OK" vs. "speichern" vs. ...
+
+* Welche Icons stehen für welche Aktionen?
+ * Beispiel: gelber Doppelpfeil (sortieren? Objekte verschieben? Objekte kopieren? Weiterblättern?)
+ * Müsste für alle Icons durchgegangen werden.
+ * IconListe
+
+* Grundlegendes
+ * Wozu dienen Icons?
+ * Wann sollen interaktive Icons, wann Buttons, wann Text-Links verwendet werden?
+ * Buttons
+ * zum Auslösen von Aktionen
+ * im Inhaltsbereich
+ * Icons
+ * zum Auslösen von Aktionen
+ * außerhalb des Inhaltsbereichs
+ * oder dort, wo für Buttons nicht genug Platz ist (z. B. in Tabellen)
+ * Textlinks
+ * zum Wechsel auf andere Seiten innerhalb von Stud.IP oder aus Stud.IP heraus
+ * nicht zum Auslösen von Aktionen
+ * überall (im Inhaltsbereich, in Infoboxen usw.)
+
+### Buttons
+
+Stud.IP verwendet klar erkennbare Buttons zum Bestätigen und Abschließend von Aktionen sowie gelegentlich zur Initiierung von Hauptaktionen im System. Haupteinsatzzweck von Buttons ist das Abschließen von Aktionen, insbesondere am Ende/Fuß eines Dialoges oder beim Löschen von Objekten.
+Als Faustformel, wann ein ein Button und wann ein Icon verwendet werden kann gilt:
+
+* Initiierung: In der Rel werden die meisten Aktionen in Stud.IP durch Icons (teilweise mit daneben gesetztem Text) initiiert. Typische Beispiele sind die Sidebar, Icons in Tabellenköpfen oder -Zeilen und sämtliche Icons in der Hauptnavigation. Dies ist einerseits durch Größeneinschränkungen begründet (Icons benötigen signifikant weniger Platz) oder das Vorhandensein einer Vielzahl an Aktionen (drei oder mehr Icons neben oder gar untereinander sind für die Usabilty besser darstellbar, als die gleiche Anzahl an Buttons). Es gibt jedoch auch Kontexte, in den Buttons zur Initiierung verwendet werden können. Dies ist insbesondere dann sinnvoll, wenn die Umgebung der Seite ausreichend Platz bietet und keine Standardelemente (Tabellen oder Content-Boxen nutzen in der Regel ausschließlich Icons) verwendet werden. Gleichzeit können Buttons verwendet werden, wenn eine Aktion besonderes Gewicht bekommt (zB. das Löschen eines Objektes).
+* Bestätigung und Abschließen: Ein guter Einsatzzweck eines Buttons ist Stets das Bestätigen und Abschließen einer Aktion, etwa am Ende eines Dialoges nach dem Eingeben einer Vielzahl von Daten. Auch das Löschen, am Ende eines Dialoges oder eines Formular sowie das Abbrechen von Dialogen sind typische Einsatzzwecke eines Buttons.
+
+Zu Beachten ist, dass ein Button stets eine Aktion als ausgeschriebenes Wort, jedoch in der Regel ohne Icon zeigt. Icons hingegen sind oft lediglich in ihrer grafischen Form zu sehen, werden jedoch häufiger (Sidebar, Aktionsmenu) auch mit Text ergänzt. Dennoch ist ein Button stets dass "gewichtigere" Interaktionselement, da er größer ist, einen Hover-Effekt bietet und auch (im Verhältnis zu Icons) durch seien große Fläche leichter zu bedienen ist. Das gilt insbesondere für Touch-Geräte.
+
+#### Beschriftung und Labelling
+
+Buttons werden in der Regel mit der Aktion des Namens als Substantive beschriftet (das "Abschließen", das "Abbrechen" und das "Speichern"). Substantiv-Verb-Konstruktionen sind zu vermeiden ("Speichern" wäre dem Button "Datei speichern" vorzuziehen). Ganze Sätze ("Diese Datei speichern.") sind nicht zulässig. Ideal ist also ein Button, der lediglich ein Wort enthält.
+Dabei soll die Beschriftung die Funktion des Buttons möglichst klar beschreiben. Es soll erkennbar sein, was passieren wird, wenn man den auf den Button drückt.
+Bei der Wortwahl sind jedoch spezifische substantivierte Verben zu verwenden. "OK" ist kein guter Button, da nicht klar erkennbar ist, was passiert. "Speichern" hingegen beschreibt einen Button korrekt.
+Im Standarddesign wird die Beschriftung automatisch zentriert ausgegeben.
+
+
+#### Erscheinungsbild
+Buttons erscheinen in Stud.IP als weiße Buttons mit starken, dunkelblauen Rand. Buttons werden als knickbares Objekt grundsätzlich Blau eingefärbt. Rote oder Grüne bzw. anders gefärbte Buttons sind nicht erlaubt. Eine gefährliche Aktion (zB. "Löschen") muss einerseits durch geeignete Platzierung des Buttons und durch weitere Absicherungen (Warndialog) geschützt werden, nicht jedoch durch Warnfarben des Buttons.
+In einigen Fällen sind noch Icons in Buttons zu sehen (Haken, X oder ähnliches). Diese Ergänzung eines Buttons durch Icons ist nicht mehr zulässig.
+
+#### Platzierung und Ausrichtung der Buttons
+
+Buttons dürfen nicht im Textfluss platziert werden und müssen immer freistehendend platziert werden. Idealerweise gibt es keine weiteren Objekte links und rechts eines oder mehrerer Buttons im gestaltbaren Bereich (zB. Dialog, Tabellenzeile, Content-Box).
+In Formularen sind Buttons linksbündig zu setzen, in Dialogen mittig. Allerdings gibt es Ausnahmen, wenn die Funktion des Buttons eines bestimmte Position impliziert. So sollten "Zurück" und "Weiter"-Buttons innerhalb eines Dialoges entsprechend linksbündig und rechtsbündig gesetzt werden.
+
+#### Reihenfolge mehrerer Buttons nebeneinander:
+Für Buttons soll eine bestimmte Reihenfolge eingehalten werden:
+1. Positiver, bestätigender Button ("Speichern", "Übernehmen", "Ja")
+2. Negativer Button ("Nein")
+3. Harmloser/abbrechender Button, der den Zustand nicht verändert ("Abbrechen", "Schließen")
+
+### Verhalten
+Stud.IP kennt neben aktiven Buttons auch inaktive Buttons. Diese sind ausgegraut und können nicht geklickt werden, weisen aber darauf hin, dass unter anderen Umständen dieser Button klickbar wäre (hier kann ein Info-"i"-Icon mit Tooltip daneben erklären, warum der Button nicht klickbar ist).
+Sollte ein Default-Button definiert sein, der bei Tastendruck (z.B. Enter) aktiviert wird, muss dies stets ein harmloser Button ("Nein", "Abbrechen" oder "Schließen") sein. Für den Fall, dass bereits Daten in einem zugehörigen Formular erfasst wurden, gibt es (zumindest in Dialogen) eine Eigenschaft, die das Schließen des Dialoges nach Eingabe ohne eine weitere Bestätigung verhindert. Generell ist darauf zu achten, dass eine Default-Option keinen Datenverlust nach sich ziehen kann.
+
+----
+
+**Allgemein** \\
+Nutze Bestätigungs-Buttons nach den folgenden Design-Patterns:
+
+
+
+| **Pattern** | **Commit buttons** |
+| ---- | ---- |
+| Frage-Dialog (mit Buttons) | Eine der folgenden spezifischen Bezeichnergruppen: Ja/Nein, Ja/Nein/Abbrechen, [Do it]/Abbrechen, [Do it]/[Don't do it], [Do it]/[Don't do it]/Abbrechen|
+| Auswahl-Dialoge | **Modaler Dialog:** OK/Abbrechen oder (Do it)/Abbrechen
+| |**Nicht-modaler Dialog**: Schließen-Button in der Dialogbox und der title bar|
+
+## Aktionsmenüs
+
+![image](../assets/3dd1c5b5758d79e52a77165d721e1665/image.png)
+
+Ein Aktionsmenü verkapselt eine Liste von kontextbezogenen Aktionen und kann an folgenden Stellen verwendet werden:
+* Bei Aktionen für ein Element in einer Liste oder Tabelle.
+ * Beispiele: Teilnehmer, Veranstaltungen, Fragebögen
+* Bei Aktionen für einen Bereich, der einen Inhalt umschließt und keine eigenen Aktions-Buttons hat.
+ * Beispiele: Tabellen, Gruppen von Personen, Widgets auf der Startseite
+
+In diesem Fall steht das Aktionsmenü ganz rechts, wo sonst die Aktions-Icons einzeln aufgeführt sind. Generell sollte ein Aktionsmenü anstelle einer Auflistung von Icons eingesetzt werden, wenn mehr als drei Aktionen direkt nebeneinander stehen (dabei zählen auch inaktive bzw. ausgeblendete Aktionen mit) oder die Icons nicht selbsterklärend sind und durch Text erklärt werden sollen.
+
+Allgemeine Richtlinien bei der Verwendung eines Aktionsmenüs:
+* Die primäre Aktion eines Elements (z.B. Bearbeiten, Anzeigen, Aufklappen) ist nicht Teil des Menüs.
+* Die primäre Aktion ist immer durch Klick auf das Element selbst (ggf. auch dessen Icon) zugänglich.
+* Wenn das Element aufklappbar ist, ist aufklappen bzw. zuklappen immer die primäre Aktion.
+* Inaktive Aktionen sollten nicht komplett versteckt, sondern nur inaktiv (grau, nicht anklickbar) angezeigt werden.
+* Ist die primäre Aktion nicht verfügbar, so ist das Element nicht anklickbar, es gibt dann nur die Aktionen im Menü.
+* Es dürfen bis zu zwei Icons außerhalb des Menüs (d.h. davor) platziert werden, falls diese oft verwendet werden.
+* Wenn eine Spalte einer Tabelle das Aktionsmenü verwendet, sollten konsistent alle Zeilen der Tabelle dieses nutzen.
+* Icons im Aktionsmenü sollten nicht verwendet werden, um den Status eines Objekts anzuzeigen.
+
+Vorgeschlagene Reihenfolge der Aktionen im Menü, sofern die einzelnen Aktionen vorhanden sind:
+* Anzeigen (oder Vorschau)
+* Herunterladen
+* Aktualisieren
+* Bearbeiten
+* Hinzufügen (oder Zuordnen)
+*
+* Verschieben
+* Kopieren
+* Exportieren
+*
+* Löschen (oder Austragen)
+
+Offene Fragen:
+* Sollte bei hinreichend breiten Displays oder durch eine Nutzereinstellung das Menü in einzelne Aktions-Icons aufgelöst werden?
+* Soll auf Mobilgeräten bzw. "kleinen" Anzeigebreiten das Aktionsmenü auch bei drei oder weniger Aktionen auftauchen?
+ * Schließt das dann auch die ggf. explizit vor dem Menü plazierten (bis zu zwei) Icons ein?
+* Sollen die Aktions-Icons generell einen Hover-Effekt bekommen? Beispiel: Cliqr
diff --git a/docs/docs/visual-style-guide/design.md b/docs/docs/visual-style-guide/design.md
new file mode 100644
index 0000000..9967ae1
--- /dev/null
+++ b/docs/docs/visual-style-guide/design.md
@@ -0,0 +1,108 @@
+---
+title: Design
+sidebar_label: Design
+---
+
+## Farben und Farbraum
+Farben sind mit das wichtigste Gestaltungsmittel. Richtig eingesetzt, können Farben Anwendern helfen Aufgaben leichter durchzuführen.
+
+Die Standard-Farben des aktuellen Stud.IP Designs wurden von den Core-Group
+GUI-Verantwortlichen in Zusammenarbeit mit einem Designer festgelegt.
+
+Das Farbschema baut auf einigen Grundfarben und festen Kontrastabständen auf.
+
+Prinzipiell kann jede Stud.IP Installation durch Anpassen der CSS-Dateien farblich verändert werden. Zu beachten ist, dass jedoch nur die base-color angepasst werden sollte. Dadurch verändern sich andere Farben entsprechend den vorgegeben Farbwerten.
+(Dies setzt allerdings voraus, dass die Farben in den less-Dateien angepasst werden und mit dem less-Compiler kompiliert werden. Auch ist zu beachten, dass die vollständige Implementierung der Stud.IP-CSS-Dateien erst in Zukünftigen Versionen umgesetzt sein wird).
+Von allen Basisfarben sind in dem Farbklima Abschwächungen in 20% Schritten vorgesehen, die automatisch über den less-Compiler erzeugt werden.
+
+## Bedeutung und Auswahl der Farben in Stud.IP
+
+Farben sind mit Bedacht zu wählen, da diese wie optische Methaphern wirken und Emotionen ansprechen. Daher ist es wichtig, Farben konsistent zu verwenden. Bislang werden Farben in Stud.IP wie folgt eingesetzt:
+
+![image](../assets/476d123391bbc2e4a825a1ea146a8465/image.png)
+
+PDF Download: [170804_Studip-Farbset.pdf](../assets/6d14189aa9093eb042bfa56eae8c7dc2/170804_Studip-Farbset.pdf)
+
+### Blau (base-color und content-color)
+Blau ist in verschiedenen Abstufungen die Standard-Hintergrundfarbe für das aktuelle Stud.IP Theme.
+Die Basisfarbe ist #28497c. Zur Hinterlegung von Content (also Inhalte innerhalb des Blatt Designs) wird der Farbwert #899ab9 als Basis genommen.
+
+Blau wird zusätzlich für klickbare Objekte verwendet, d. h. mit blau werden Text-Links und klickbare Icons gekennzeichnet.
+
+Blau ist auch die Hintergrundfarbe für [Messageboxen](MessageBox) mit Informationsmeldungen.
+
+Die base-color kann von Betreiber angepasst werden an eigene Farben, zB. um dem eigenen CD zu entsprechen. Die content-color sollte nicht angepasst werden.
+
+### Grau (light-gray, dark-gray)
+
+Zur Hinterlegung verschiedener Bereiche (zB. Infoboxen, Navigation) existieren unterschiedliche Grautöne, die frei verwendet werden dürfen. Die Basiswerte sind #69767f (light-gray) und #3c454e (dark-gray).
+Inhalte (also Tabellen, Textbeiträge, Nachrichten, Formulare) dürfen nur mit der content-color (blau) hinterlegt werden. Andere Objekte können auch mit Grau hinterlegt werden.
+
+## Markierungsfarben
+
+Neben den Grundfarben werden Farben auch für unterschiedliche Markierungen/Kategorisierungen verwendet. Die dafür erlaubten Farben sind ebenfalls definiert:
+
+![image](../assets/1cde32e97e840ad35cd7840e4d61b016/image.png)
+
+### Rot
+Rot wird als Signalfarbe an mehreren Stellen eingesetzt:
+
+Rot kennzeichnet zum einen kritische Aktionen und wird somit beispielsweise als Rahmenfarbe für Fehlermeldungen verwendet. Auch das Icon in einer Fehlermeldung ist rot gefärbt.
+
+Zum anderen wird alles Neue (aus Sicht des jeweiligen Nutzes) in Rot hervorgehoben. So kommen rote Icons beispielsweise auf der Seite "Meine Veranstaltungen" zum Einsatz, wenn einer der Bereiche einer Veranstaltung für den Nutzer neue Inhalte enthält. Auch in den Bereichen der Veranstaltungen gibt es an mehreren Stellen rote Markierung für neue Beiträge.
+
+Basisfarbton für rot ist: `#d60000`
+
+### Grün
+Grün wird lediglich für positive Rückmeldungen verwendet. Grün ist z. B. die Rahmenfarbe Meldungen mit Erfolgsbestätigung.
+
+In der Gestaltung von Stud.IP.Inhalten oder anderen Elementen darf grün nicht eingesetzt werden!
+
+### Gelb (activity-indikator)
+Gelb wird lediglich als Markierungsfarbe genutzt.
+
+Beispielsweise ist der Indikator, welche Ansicht in einer Seite mit mehreren Ansicht gewählt wurde, ein gelber Pfeil.
+
+Im Forum oder dem Wiki markiert die Farbe Gelb Fundstellen in der Trefferliste.
+
+Gelbe Verschiebepfeile zum Umsortieren von Objekten sind in der aktuellen Gestaltung nicht mehr zulässig.
+
+Basisfarbe ist `#ffbd33`
+
+### Schwarz und Weiß
+Schwarz und Weiß werden als Schrift- und Kontrastfarbe verwendet. Schrift und Symbole werden je nach Hintergrund schwarz oder weiß gezeichnet.
+
+### Hinweise
+Die hexadezimalen Werte der Farben sind in LESS-Dateien (`public/assets/stylesheets/mixins/colors.less`) definiert und müssen bei Anpassungen mit einem entsprechenden Less-Compiler in die Stylesheets übertragen werden.
+Das händische Anpassen einzelner Farbwerke in den CSS-Dateien wird ausdrücklich nicht empfehlen, da einige Farben auch in Abhängigkeiten zueinander (etwa von der Base-color) definiert/erzeugt werden.
+
+Die Verwendung von Farben für Icons wird im Abschnitt zu [Icons](Visual-Style-Guide#Icons) ausführlicher beschrieben.
+
+### Allgemeine Hinweise zur Farbauswahl
+
+#### Farben mit Bedacht und sparsam verwenden
+Farben sollten sparsam verwendet werden. Um Bereiche im [Inhaltsbereich einer Stud.IP Seite](http://hilfe.studip.de/develop/Style/DesignSeitenlayout) durch Farbe zu kennzeichnen, empfiehlt es sich laut ISO 9241-12 nicht mehr als sechs (zusätzlich zu schwarz und weiß) verschiedene Farben zu verwenden. Die verwendeten Farben sollten durch den Anwender gut unterscheidbar sein.
+
+#### Farbe nicht als alleiniges visuelles Hilfsmittel verwenden
+Farben sollten nicht als einziges visuelles Mittel verwendet werden, um Informationen zu vermitteln oder Elemente zu kennzeichnen. Für farbenfehlsichtige Nutzer ist es möglicherweise schwierig zwei Objekte zu unterscheiden, die sich nur in ihrer Farbe unterscheiden. Unterschiede sollten zusätzlich durch z. B. unterschiedliche Formen, Positionen oder eine textuelle Beschreibung gekennzeichnet werden.
+
+#### Farbtöne mit gleichen Sättigungsgrad verwenden
+Um eine harmonisches Farbdesign zu erreichen, sollten Farbtöne verwendet werden, die den gleichen Sättigungsgrad aufweisen. Sättigung (bzw. Buntheit) bezeichnet den Grauanteil einer Farbe. Je weniger Grau eine Farbe enthält, desto leuchtender wirkt sie.
+
+Große Flächen sollten nicht in leuchtenden (gesättigten) Farben gestaltet werden. Diese werden schwer lesbar und können mitunter Kopfschmerzen verursachen.
+
+#### Ist der Kontrast zwischen Elementen und ihrem Hintergrund ausreichend?
+Wenn sich der Farbton von Vorder- und Hintergrundfarben zu sehr ähnelt, sind Unterschiede schwer erkennbar.
+
+Tipps zur Überprüfung des Kontrastes einer Farbkombination:
+* Um zu überprüfen, ob ein ausreichender Kontrast vorhanden ist, empfiehlt es sich die Seite schwarz-weiß zu drucken. Wenn der Ausdruck gut lesbar ist, ist typischerweise ein ausreichender Kontrast vorhanden.
+* Mit dem Online-Tool [Color Contrast Checker](http://www.snook.ca/technical/colour_contrast/colour.html) kann direkt überprüft werden, ob ein ausreichender Kontrast zwischen zwei Farben vorhanden ist.
+* Auf manchen Betriebsystemen kann auch die Darstellung bereits als Grundeinstellung in Graustufen geschaltet werden, um die Kontraste zu testen.
+
+#### Unruhige und ablenkende Hintergründe vermeiden
+Ungünstig sind Muster oder Bilder im Hintergrund, die einen ungleichmäßigen Kontrast verursachen, das Auge vom Text ablenken und damit die Lesbarkeit erschweren.
+
+### Weiterführende Links
+* Tutorial: Farben im Webdesign [http://metacolor.de/](http://metacolor.de/)
+* [Farb/Kontrastanalysen mit Bezug auf a11y-Kriterien](http://www.blog.mediaprojekte.de/grafik-design/farb-kontrast-analyse-die-accessibility-der-farben-testen)
+* http://e-campus.uibk.ac.at/planet-et-fix/M8/8.5.2_Praesentationen/links/farben.html
diff --git a/docs/docs/visual-style-guide/dialoge.md b/docs/docs/visual-style-guide/dialoge.md
new file mode 100644
index 0000000..d1eff80
--- /dev/null
+++ b/docs/docs/visual-style-guide/dialoge.md
@@ -0,0 +1,74 @@
+---
+title: Dialoge
+sidebar_label: Dialoge
+---
+
+
+Dialoge werden verwendet um Eingaben oder Bestätigungen vom Benutzer einzuholen.
+Seit der Einführung der Sidebar gelten Dialoge auch grundsätzlich als Repräsentation von Aktionen.
+Aktionen verbleiben dabei auf der jeweiligen Seite und vermeiden einen Kontextwechsel
+
+Trotzdem sollten Dialoge nicht inflationär eingesetzt werden. Wenn möglich sollte eine Interaktion direkt auf der Seite stattfinden,
+auf der auch die zu bearbeitenden Informationen oder Elemente dargestellt werden.
+
+
+## Allgemeines
+Dialoge sollten einfach gehalten und nicht zu komplex sein, d. h. nur eine zugehörige Aktion sollte pro Dialog ausgeführt werden.
+
+Dialoge sollten selbst erklärend sein und möglichst wenig (Erklärungs-) Texte enthalten.
+
+Dialoge sollten nicht gestapelt werden, d. h. ein Dialog sollte sich nicht in einem Dialog öffnen (außer z . B. Datepicker, wo kein Button gedrückt werden muss). Für komplexere Dialoge sollten Wizards verwendet werden, in dene mehrere Dialoge hintereinander geschaltet sind oder Bereich innerhalb der Dialoge auf- und zugeklappt werden.
+
+## Modale und nicht-modale Dialogfenster
+Bei modalen Dialogfenstern kann der Benutzer nicht in anderen Fenstern der Anwendung weiterarbeiten, sondern nur im Dialogfenster. Im Unterschied dazu erlauben nicht-modale Dialogfenster dem Benutzer auch die Interaktion mit dem Hintergrundfenster. Nicht-modale Dialogfenster werden in Stud.IP beispielsweise beim Stundenplan zur Eingabe von Terminen verwendet. Die Mehrzahl der Dialoge in Stud.IP ist jedoch modal.
+
+Beim Aufruf eines modalen Dialogfensters wird das Hintergrundfenster durch geeignete optische Manipulation ("Ausgrauen" bzw. abdunkeln mit einem Overlay in Dunkelblau) als inaktiv gekennzeichnet.
+
+Dialoge sind abzugrenzen von Notifications, die nie modal sind und nicht als eigenes Fenster erscheinen.
+
+## Popup-Fenster
+
+Popup-Fenster, d. h. sich separat öffnende Fenster, dürfen nicht verwendet werden.
+
+## Eigenschaften
+Dialogfenster sind meist [formularartig](Visual-Style-Guide#Formulare) aufgebaut.
+
+Dialoge sollten lesbar sein, ohne dass ein Scrollen im Dialog erforderlich wird. Eine Ausnahme bildet das vertikale Scrollen: Wenn es sich nicht vermeiden lässt (etwa weil Inhalte, lange Listen oder Aufklappelemente nicht das Dialogfenster passen) darf innerhalb des Dialogfensters gescrollt werden.
+
+Die Seitengröße innerhalb eines Dialogfensters sollte sich während seiner Bearbeitung nicht ändern. Ausgenommen von dieser Einschränkung ist beispielsweise das dynamische Nachladen von Elementen einer Drop-Down-Liste oder das dynamische Einblenden von Ausfüllhinweisen bei Pflichtfeldern.
+
+## Verhalten
+Wenn man neben ein Dialogfester klickt, sollte sich dieses nicht schließen.
+
+Wenn der Benutzer den Button Escape drückt, schließt sich das Dialogfenster, es sei denn, es wurden bereits Eingaben gemacht. Hier muss der Auto-Formsaver aktiviert werden, so dass der Nutzer auf eventuelle verlorengehende Inhalte hingewiesen wird.
+
+
+### Schematischer Aufbau eines Dialogfensters
+![image](../assets/ce19cb16e52e35fa80ad2fd66ee7fbac/image.png)
+
+#### Layout/Design
+
+Grundsätzlich sind Dialoge gestalterisch so aufgebaut, dass sie von einer dunkelblauen Kopfzeile (Stud.
+IP-Brand-Color, siehe [design](Design)) eingeleitet werden und einen weißen Hintergrund haben. Sie haben einen
+dünnen weißen Rand, einen leichten Schatten und dunkeln die dahinterliegende Seite ab. Buttons haben einen separaten Footer, analog zu Tabellen oder Formularen.
+
+Beispiel für das Design:
+
+![image](../assets/c37f69398215d78b12784d3428c89a9c/Bildschirmfoto_2021-11-15_um_15.35.11.png)
+
+#### Text in der Titelleiste
+Der Titeltext sollte aussagekräftig und spezifisch sein, damit Benutzer genau wissen, was sie tun sollen. Eine Dopplung zum Content muss vermeiden werden.
+
+#### Buttons
+Jeder Dialog ein X-Icon rechts in der Titelleiste, um den Dialog schließen zu können. Zusätzlich gibt es einen separaten "Abbrechen"/"Schließen"-Button, da viele Benutzer das x-Icon in der Titelleiste übersehen.
+
+Auf dem Übernehmen-Button (accept-Button) wird ein Häkchen-Icon angezeigt.
+
+Der Text auf dem Übernehmen-Button sollte ein spezifisches Verb sein wie beispielsweise "Löschen" oder "Anlegen" und nicht nur "OK".
+
+### Sicherheitsabfragen
+Sicherheitsabfragen werden verwendet insbesondere beim Löschen wichtiger Elemente oder bei anderen kritischen und unwiderruflichen Aktionen.
+
+Sicherheitsabfrage sind eine vereinfachte Form des modalen Dialogs.
+
+Sie enthalten einen Hinweis- oder Fragetext und zwei Buttons zum Bestätigen und Verwerfen.
diff --git a/docs/docs/visual-style-guide/einleitung.md b/docs/docs/visual-style-guide/einleitung.md
new file mode 100644
index 0000000..13affb3
--- /dev/null
+++ b/docs/docs/visual-style-guide/einleitung.md
@@ -0,0 +1,84 @@
+---
+title: Einleitung
+sidebar_label: Einleitung
+---
+
+__Der Stud.IP Styleguide ist stets "work in progress".__
+
+Für aktuelle Designfragen bieten wir vom Styleguide-Team (André, Cornelis, Marcus, Marco) bis auf Weiteres jeden Donnerstag um 14.00 eine Design-Sprechstunde per Videokonferenz an. Die Konferenz findet in Skype statt.
+
+
+Stud.IP begleitet die Studierenden durch viele Jahre ihres Studiums und ist für die Lehrenden ein ständiger Begleiter bei ihrer täglichen Arbeit. Dieser Umstand verlangt Stud.IP eine gewisse beständige Motivationsfähigkeit ab, dass neue Funktionen sich konsistent und harmonisch in das Gesamtbild einfügen und dass sich ständig wiederholende Arbeitsabläufe möglichst einfach und zeitsparend erledigt werden können.
+Diese und weitere Anforderungen spiegeln sich in der Stud.IP-Design Philosophie wieder, die diesen Style-Guide leiten soll:
+
+Einfache Bedienbarkeit statt großem Funktionsumfang: Weniger ist mehr und daher soll Stud.IP nur jene Funktionen bereitstellen, die dem größten Teil der Nutzer hilft. Es gilt, die Bedürfnisse von 80% der Nutzern zu unterstützen, statt mit den besonderen Anforderungen der restlichen 20% die größere Gruppe zu überfordern.
+Ein gleicher Funktionsumfang für alle Veranstaltungen statt individuelle Anpassung an spezielle Bedürfnisse: Nutzer erhalten so eine verlässliche Umgebung unabhängig vom Einsatz in ganz unterschiedlichen Lehr- und Lernsituationen.
+Verlässlichkeit in der Bedienung: Gleichartige Funktionen sollen stets konsistent umgesetzt werden. Bewährte Muster kommen systemweit zum Einsatz. Neue Ansätze sollen konsequent genutzt werden, so dass die gesamte Software von einer Weiterentwicklung profitiert, auch wenn dieses einen größeren Aufwand bei der Umsetzung bedeutet.
+Behutsame Integration neuer Technologien: Die Nutzung neuer Internet-, Browser- oder Medientechnologien sollte nicht sollte nur verwendet werden, wenn ein Verbesserung für einen großen Teil der Nutzer technisch verfügbar ist. Gleichzeitig muss sichergestellt sein, dass ältere Konfigurationen in angemessenem Maße soweit unterstützt werden, dass eine Bedienbarkeit gewährleistet wird.
+Stud.IP ist ebenso auf mobilen Geräten wie auf Dektop-Rechnern vollständig benutzbar. Dabei gilt jedoch kein reines "mobile first" Prinzip: Mobile Seiten können an bestimmen Bereichen (insbesondere für Administratoren und Administratorinnen) eingeschränkte Funktionalitäten bieten.
+
+## Vier allgemeine Gestaltungsprinzipien für Stud.IP
+
+"...the basic principles of design that appear in every well-designed piece of work."
+> Robin Williams, The Non-Designer's Design Book
+
+### Visuelle Gestaltung
+
+Bezüglich der visuellen Gestaltung von Elementen innerhalb einer Seite gibt es viel zu beachten. Eine einfache Möglichkeit die Nutzbarkeit der jeweiligen Seite schnell zu erhöhen, ist die Beachtung der vier C.R.A.P.-Prinzipien:
+* Kontrast (Contrast)
+* Wiederholung (Repetition)
+* Ausrichtung (Alignment)
+* Nähe (Proximity)
+
+Die im Folgenden vorgestellten Regeln gibt es auch als Poster Attach:studip-design-poster-final.pdf.
+
+### Kontrast (Contrast)
+* Kontrast dient als gestalterisches Mittel, um verschiedene Seitenelemente klar unterscheidbar voneinander abzuheben.
+* Kontrast kann dazu verwendet werden, um auf wichtige Inhalte zu fokussieren.
+* Nicht gleiche Elemente sollten sich deutlich voneinander unterscheiden.
+* Kontrast kann über verschiedene gestalterische Elemente realisiert werden z.B. Schriftart, Farbe, Größen, Formen, Nähe, etc.
+
+#### Negativbeispiele
+* Dateien hochladen in Veranstaltungen - 9 gleichaussehende Button in einer Reihe
+
+### Repetition (Wiederholungen):
+* Wiederholungen schaffen Konsistenzen im System
+* Ein konsistentes Design verbessert die Nutzbarkeit des Systems
+* Konsistenzen können durch den wiederholten Einsatz verschiedenster gestalterischer Elemente erzeugt werden:
+ ** Abstrakt: Menüstrukturen, Funktionsabläufe
+ ** Konkret: Icons, Schriften, Bezeichnungen
+
+#### Beispiele
+* Hauptnavigationsleiste bleibt gleich
+* Footer bleibt gleich
+* Infobox/Sidebar
+
+#### Negativbeispiele
+* verschiedene Suchformularvarianten bei der Personensuche
+
+### Ausrichtung (Alignment):
+* Inhaltselemente sollten nicht willkürlich auf einer Seite platziert, sondern an anderen Elementen horizontal und vertikal ausgerichtet sein.
+ Tipps
+* Text sollte links- oder rechtsbündig ausgerichtet sein, aber nicht beides gleichzeitig auf einer Seite.
+* Abstände sollten gleichmäßig sein.
+* Kein rechts ausgerichteter Text in einer rechten Seitenspalte. Dies erzeugt zu viel Whitespace.
+* Hilfslinien zeichnen, Abweichungen festzustellen
+
+#### Negativbeispiel
+* Assistent zum Anlegen von Veranstaltungen
+
+### Nähe (Proximity)
+* Inhaltselemente, die nah beieinander stehen, erwecken den Eindruck, dass sie zusammen gehören:
+* Verwandte Inhaltselemente sollten daher räumlich nah zueinander gruppiert werden.
+* Zwischen unterschiedliche Inhaltselementen sollte genug Abstand vorhanden sein, weil sonst ein Eindruck von Zusammengehörigkeit erweckt wird.
+* Die Gruppierung der Elemente erhöht die Übersichtlichkeit und Inhalte werden besser strukturiert.
+
+#### Negativbeispiel
+* Gruppenverwaltung - TeilnehmerInnen einer Veranstaltung oder Kalender/Adressbuch - Button zum Hinzufügen zu einer Gruppe ist zu weit weg
+
+### Weiterführende Informationen
+* http://www.userfocus.co.uk/articles/A_CRAP_way_to_improve_usability.html
+* http://www.dailyblogtips.com/crapthe-four-principles-of-sound-design/
+* http://lab.christianmontoya.com/designing-with-crap/designing-with-crap-cc.pdf
+* http://www.colorado.edu/AmStudies/lewis/Design/graprin.htm#summary
+* http://blog.teamtreehouse.com/how-crap-is-your-site-design
diff --git a/docs/docs/visual-style-guide/elementlisten.md b/docs/docs/visual-style-guide/elementlisten.md
new file mode 100644
index 0000000..9c3c310
--- /dev/null
+++ b/docs/docs/visual-style-guide/elementlisten.md
@@ -0,0 +1,133 @@
+---
+title: Elementlisten
+sidebar_label: Elementlisten
+---
+
+In Stud.IP werden verschiedenste Objekte/Elemente in Listenform ausgegeben: Personen (z.B. Veranstaltungsteilnehmer), Veranstaltungen (z.B. auf der Seite »Meine Veranstaltungen«), Einrichtungen, News, Votings, Studiengänge und vieles mehr.
+
+Durch die konsequente Verwendung der neuen Stud.IP-Tabellen ist die Einheitlichkeit der Darstellung bereits stark verbessert worden. Einige alte Seiten müssen noch auf das neue Design umgestellt bzw. templateisiert werden (Stand August 2015)
+
+
+## Darstellung
+* Die einzelnen Elemente einer Liste werden in einzelnen Zeilen **untereinander** angeordnet.
+* In den Zeilen einer Elementliste findet **kein Zeilenumbruch** statt.
+* Jede Zeile enthält den Namen des Elements bzw. andere, möglichst wenige Informationen, die das Element identifizieren (bei Terminen z. B. Datum und Uhrzeit).
+* Sofern die Elemente einander hierarchisch untergeordnet werden sollen, wird diese **Hierarchie durch Einrückungen** dargestellt
+* als Design gilt das aktuelle Tabellendesign von Stud.IP (siehe dort).
+* Jede Elementliste sollte **tabellarisch** aufgebaut sein. Dies erhöht die Lesbarkeit der Inhalte.
+ * Zu einer Tabelle gehört ein **Tabellenkopf**, der für jede Spalte der Tabelle eine Überschrift liefert.
+ * Die **horizontale Ausrichtung** innerhalb einer jeden Spalte ist abhängig von den Inhalten und deren Zweck sinnvoll zu wählen.
+ * Die Ausrichtung in Tabellenkopf und Tabellenkörper sollte gleich sein.
+ * Bei langen Elementlisten kann es sinnvoll sein, die Liste durch **Zwischenüberschriften** zu unterbrechen. In solchen Fällen ist der Tabellenkopf über jeder Teil-Liste zu wiederholen.
+* Wenn die Elementliste für die Darstellung auf einer einzigen Seite zu lang ist, ist eine **Paginierung** vorzusehen.
+ * Zum Blättern werden rechts unterhalb der Elementliste die Seiten angezeigt, auf die sich die gesamte Liste verteilt.
+ * Bei einem Klick auf eine Seitennummer gelangt man auf die jeweilige Seite
+ * Zusätzlich gibt es links neben den Seitennummern einen Link "zurück" (außer auf Seite 1) sowie rechts neben den Seitennummern den Link "weiter" (außer auf der letzten Seite).
+ * (zu weiteren Details siehe Paginierung auf score.php)
+
+## Aktionen
+* Einzelaktionen
+ * Einzelaktionen sind Aktionen, die sich auf einzelne Elemente einer Liste beziehen.
+ * Bei Einzelaktionen ist zwischen Standardaktionen und erweiterten Aktionen zu unterscheiden.
+ * Standardaktionen sind Aktionen, die auf die meisten Arten von Listenelementen angewendet werden können. Sie werden durch Icons (nicht durch Buttons!) in der Überschriftszeile des Listenelements ausgelöst. Standardaktionen sind
+ * Löschen
+ * Auf- und Zuklappen
+ * Sortieren/Reihenfolge ändern
+ * Erweiterte Einzelaktionen sind Aktionen, die über die Standardaktionen hinausgehen. Sie sind nur für einzelne Arten von Listenelementen anwendbar und/oder erfordern umfangreichere Formulare o.ä., die allein schon vom Platz her nicht in die Überschriftszeile passen würden. Beispiele für erweiterte Aktionen sind das Einstellen der Laufzeit von Evaluationen oder das Buchen eines Raumes für einen Termin.
+ * Um erweiterte Einzelaktionen ausführen können, muss der Benutzer das jeweilige Listenelement erst aufklappen. Unter dem Listenelement öffnet sich dann ein Bereich, in dem die Interaktionselemente angezeigt werden, mit denen die Aktion ausgeführt werden kann.
+ * Löschen
+ * Das Löschen eines Listenelements wird durch den Klick auf ein Mülleimersymbol rechts in der Titelzeile des Listenelements durchgeführt (nicht durch einen Löschen-Button, der erst nach dem Aufklappen sichtbar wird).
+ * Auf- und Zuklappen
+ * Oft braucht man die Möglichkeit, ein Listenelement aufzuklappen. Dies ist regelmäßig dann der Fall, ...
+ * ... wenn man zu einem Element zusätzliche Informationen einblenden möchte, die nicht in die Überschriftszeile des Listenelements passen (z.B. Informationen über Teilnehmer einer Veranstaltung)
+ * ... wenn man umfangreiche Bearbeitungsmöglichkeiten bereitstellen möchte, die nicht in die Überschriftszeile des Listenelements passen (z.B. bei Umfragen oder Evaluationen)
+ * ... wenn man weitere Listenelemente einblenden möchte, die dem Listenelement hierarchisch untergeordnet sind (z.B. in der Veranstaltungshierarchie)
+ * ... wenn man Objekte einblenden möchte, die in dem Listenelement enthalten sind (z.B. Personen in einer Statusgruppe)
+ * ... wenn man Objekte einblenden möchte, die dem Listenelement auf die andere Art zugeordnet sind (z.B. Einzeltermine eines regelmäßigen Termins)
+ * Zum Auf- und Zuklappen ist ein ">"-Icon am linken Ende der Titelzeile anzuzeigen. Ein Klick darauf klappt das jeweilige Listenelement auf. Das Icon wird dabei gegen eines ausgetauscht, dessen Spitze nach unten zeigt.
+ * Bei aktiviertem JavaScript sollte das Auf- und Zuklappen ohne Page Reload realisiert werden.
+ * Grundsätzlich sollte es durch das Aufklappen eines Listenelements möglich sein, die Grundinformationen zu ändern, die in der Titelzeile des Listenelements enthalten sind. Dazu wird in der Titelzeile dort, wo im zugeklappten Zustand die jeweilige Information/Eigenschaft ausgegeben wird, ein entsprechendes Formularfeld angezeigt, das mit den aktuellen Werte gefüllt ist.
+ * Manchmal ist es sinnvoll, alle Listenelemente auf einmal auszudrucken. Dies wird durch ein Icon realisiert, das einen Pfeil nach oben und einen nach unten zeigt. Dieses Icon wird in einer Zeile zwischen dem Tabellenkopf und dem Tabellenkörper eingefügt.
+ * Sortieren/Reihenfolge ändern
+ * Eine Elementliste sollte grundsätzlich sortierbar sein. Dadurch wird es den Benutzern ermöglicht, die Inhalte ihren Wünschen und Nutzungszielen entsprechend darzustellen. Von einer Sortierbarkeit kann bei hierarchischen oder geschachtelten Listen verzichtet werden.
+ * Die Liste sollte nach den Kriterien sortierbar sein, die durch die Überschriften im Tabellenkopf repräsentiert sind, sofern dies sinnvoll möglich ist.
+ * Beim ersten Aufruf der Liste sollte diese bereits nach einem sinnvollen Kriterium sortiert sein.
+ * Nach welchem Kriterium und in welcher Richtung (auf- oder absteigend) eine Liste sortiert ist, wird durch ein kleines blaues Dreieck rechts neben der jeweiligen Überschrift im Tabellenkopf angezeigt. Eine aufsteigende Sortierung wird durch ein nach oben zeigendes Dreieck, eine absteigende Sortierung durch ein nach unten zeigendes Dreieck angezeigt.
+ * Die Sortierung der Liste erfolgt durch den Klick auf die jeweilige Überschrift im Tabellenkopf oder auf das gelbe Dreieck. Ist die Liste bereits nach dem
+ Kriterium sortiert, das man anklickt, so wird die Reihenfolge der Sortierung umgekehrt.
+ * Die Möglichkeit des Klicks auf den Namen wird durch blaue Schrift (Standardfarbe für Links) dargestellt, die Farbe entspricht der Farbe des Dreiecks.
+ * In bestimmten Fällen kann es sinnvoll sein, die Reihenfolge der Elemente manuell festzulegen (also keine Sortierung nach einem Kriterium).
+ * Hat der Benutzer in seinem Brwoser JavaScript aktiviert, so sollte er die Möglichkeit haben, die Reihenfolge per Drag and Drop festzulegen.
+ * (spezifizieren)
+ * Wenn im Browser des Benutzers JavaScript ausgeschaltet ist, sollten gelbe Sortierpfeile im rechten Bereich der jeweiligen Zeile zur Verfügung stehen, mit denen die Reihenfolge festgelegt werden kann. Diese sind in zwei Spalten angeordnet: Die Pfeile, die nach unten zeigen, befinden sich in der linken Spalte, die Pfeile, die nach oben zeigen, in der rechten Spalte. Die oberste Zeile enthält nur einen Pfeil nach unten, die unterste Zeile nur einen pfeil nach oben.
+ * Wenn die Elementliste sehr lang ist, kann es für den Benutzer schwierig werden, einzelne Listenelemente an eine weiter entfernte Position innerhalb der Liste zu befördern. In so einem Fall können dem Benutzer zusätzlich Optionsfelder und gewinkelte Pfeile zur Verfügung gestellt werden, mit denen einzelne Listenelemente ausgewählt und an eine bestimmte Stelle einsortiert werden können (Beispiel: Nutzerverwaltung in Einrichtungen).
+* Sammelaktionen
+ * Eine Sammelaktion ist eine Aktion, die auf mehrere Listenelemente gleichzeitig angewendet wird.
+ * Das Auswählen der Listenelemente für eine Sammelaktion erfolgt mittels Checkboxes links in den Titelzeilen der Listenelemente.
+ * Unterhalb der Elementliste steht eine Dropdown-Box zur Verfügung, aus der man die gewünschte Aktion auswählen kann. Mit einem Klick auf einen Button "OK" wird die Sammelaktion ausgeführt.
+ * Zusätzlich stehen in der Dropdown-Box Optionen zur Veränderung der Auswahl zur Verfügung (mindestens "alle auswählen", "keine auswählen" und "Auswhl umkehren").
+
+## Auf- und Zuklappen
+
+* Sofern es zu einem Element mehr Informationen gibt, als man es in einer Textzeile darstellen kann, soll dies durch Auf- und Zuklappen des Elements dargestellt werden. Hierzu ist ganz links in der entsprechenden Zeile ein nach rechts weisendes Dreieck auszugeben. Ein Klick auf dieses Dreieck bewirkt, dass unterhalb des gewählten Elements die Detailinformationen eingeblendet werden, ggf. mit Möglichkeiten, diese zu bearbeiten. Die Elemente unter dem aufgeklappten Element rutschen entsprechend weiter nach unten. Das Dreieck, dessen Anklicken das Aufklappen bewirkt hat, weist nach unten, wenn das Element aufgeklappt ist. Ein weiterer Klick darauf "schließt" das aufgeklappte Element wieder. Das Auf- und Zuklappen soll zusätzlich durch Klicken auf den Namen des Elements ermöglicht werden.
+* Wie soll das Bearbeiten der Elementeigenschaften umgesetzt werden? Soll es immer identisch funktionieren? Falls nicht: Welche Abweichungen sollen erlaubt sein, und unter welchen Bedingungen sollen sie erlaubt sein?
+* Variante 1: Beim Aufklappen bleibt die Titelzeile unverändert, sämtliche Informationen (also auch die in der Titelzeile eingeblendeten) werden unterhalb der Titelzeile in Formularfelder eingeblendet und können mit einem Klick auf den Button "übernehmen" gespeichert werden. Temporär widersprechen sich also die Angaben aus der Titelzeile und dem Formularfeld, in dem man diese Information gerade ändert. (Beispiel: Ablaufplan, Gruppen/Funktionen in Einrichtungen)
+* Variante 2: Beim Aufklappen ist zunächst noch nichts bearbeitbar. Dazu muss man erst auf den Button "bearbeiten" klicken (der erst durch Aufklappen sichtbar wird). Die in der Titelzeile enthaltenen Attribute werden in der Titelzeile bearbeitbar gemacht; unterhalb dieser werden zuätzliche Informationen bearbeitbar gemacht. Ein Klick auf den Button "übernehmen" (unterhalb der bearbeitbaren Informationen) speichert alles. (Beispiel: Dateibereich, Forum)
+* Variante 3: Wie Variante 2, aber die Informationen aus der Titelzeile werden nicht in der Titelzeile selbst, sondern (wie in Variante 1) unterhalb der Titelzeile bearbeitbar gemacht. (Beispiel: Literaturverwaltung)
+* Variante 4: Durch Aufklappen werden bereits alle Informationen bearbeitbar gemacht. Die Information aus der Titelzeile wird daselbst bearbeitbar gemacht. Ein Klick auf den Button "übernehmen" unterhalb sämtlicher Informationen speichert die Änderungen und schließt das Element. (Beispiel: Einzeltermin auf Raumzeitseite)
+* Variante 5: Durch Aufklappen (oder durch Klick auf ein Bearbeiten-Icon in der Titelzeile) wird die Information aus der Titelzeile bearbeitbar gemacht und durch Klick auf den Button "übernehmen" (der sich innerhalb der Titelzeile befindet) gespeichert. Gleichzeitig wird das Element zugeklappt. (Beispiel: regelmäßige Zeit auf Raumzeitseite)
+* Variante 6: Durch Aufklappen werden alle Informationen bearbeitbar gemacht. Die Information aus der Titelzeile wird daselbst bearbeitbar gemacht, zusätzlich werden unterhalb der Titelzeile weitere Informationen bearbeitbar eingeblendet. Der Klick auf den Button "übernehmen" (unterhalb sämtlicher Informationen) speichert die Änderungen und schließt das Element. (Beispiel: Gruppierungs- und Fragenblöcke in Evaluationen)
+* Variante 7: Durch Aufklappen werden Zusatzinformationen und -eigenschaften eingeblendet und bearbeitbar gemacht und durch Klick auf den Button "übernehmen" gespeichert, wobei das Element gleichzeitig geschlossen wird. Die Information aus der Titelzeile kann hierbei jedoch nicht geändert werden. Dazu muss man auf den Button "bearbeiten" in der Titelzeile klicken. Dies führt zu einer neuen Seite, auf der man die Information aus der Titelzeile, aber auch eine Reihe weiterer Informationen/Eigenschaften des Elements bearbeiten kann. (Beispiel: Evaluationen und Evakuationsvorlagen)
+* Variante 8: Wie Variante 7, nur wird hier durch bloßes Aufklappen nichts bearbeitbar gemacht. Vielmehr muss man zum Bearbeiten sämtlicher Informationen auf den Button "bearbeiten" in der Titelzeile klicken, wodurch man auf eine andere Seite gelangt. (Beispiel: Votings)
+* Variante 9: Das Element ist nicht aufklappbar. Um die Elementeigenschaften zu ändern, klickt man auf den Button "bearbeiten" in der Titelzeile. Dieser führt auf eine neue Seite, auf der man sämtliche Eigenschaften bearbeiten kann. Ein Klick auf den Button "übernehmen" (oder auf einen Text-Link "zurück" o. ä.) führt zurück zur vorherigen Seite. (Beispiel: News, Klausuren und Übungsblätter in Vips)
+* Variante 10: In der Titelzeile befindet sich ein Bearbeiten-Icon. Ein Klick darauf verändert die Hintergrundfarbe der Titelzeile, um anzuzeigen, dass es sich im Bearbeiten-Modus befindet. Oben auf der Seite wird ein bereits bestehendes Formular (das zum Festlegen der Eigenschaften neu anzulegender Elemente verwendet wird) durch ein ähnliches Formular ersetzt, das mit den Werten des ausgewählten Elements befüllt wird, wodurch man sie bearbeiten kann. Ein Klick auf den Button "speichern" innerhalb dieses Formulars übernimmt die Änderungen, setzt die Hintergrundfarbe der Titelzeile sowie das Formular zurück. (Beispiel: Gruppen/Funktionen in Veranstaltungen, Gruppenverwaltung in Vips funktioniert ähnlich)
+
+## Löschen
+
+Sofern es möglich sein soll, einzelne Listenelemente zu löschen, so soll dies durch ein Mülleimer-Icon symbolisiert werden. Dieses Icon befindet sich ganz rechts in der jeweiligen Zeile. Ein Klick darauf bewirkt, dass das Element gelöscht bzw. aus dem jeweiligen abstrakten "Container" (Veranstaltung, Statusgruppe usw.) entfernt wird. Das Mülleimer-Icon steht sowohl im zu- als auch im aufgeklappten Zustand zur Verfügung.
+
+## Sortieren
+
+### Frei
+* Sofern die Reihenfolge der Elemente dauerhaft geändert werden soll (wenn also nicht nur die momentane Darstellung von z. B. Suchergebnissen verändert werden soll), sind Interaktionselemente vorzusehen, mit denen man dies bewerkstelligen kann.
+* Beispiele für sortierbare Elemente: Personen, Dateien, Forumsbeiträge, Veranstaltungen, Literatur, Termine, Themen, Ressourcen, Statusgruppen, News, Votings, Evaluationen, Nachrichten, Raumanfragen, Studienbereiche, ...
+* JavaScript aktiviert
+ * Drag and Drop: Hierfür ist ein Anfasser-Icon am linken Rand der jeweiligen Zeile anzuzeigen. (Befindet sich dort ein Auf- und Zuklapp-Dreieck, so wird der Anfasser direkt rechts daneben angezeigt.) Klicken und Festhalten dieses Icons bewirkt, dass man die jeweilige Zeile in gerader Linie nach oben oder unten bewegen und durch Loslassen an einer andere Stelle einsortieren kann. Dieses Drag and Drop steht sowohl im auf- als auch im zugeklappten Zustand zur Verfügung.
+* JavaScript deaktiviert
+ * Sortierpfeile: Anstelle der Riffelung sind gelbe Doppeldreiecke anzuzeigen. Das obere und untere Element einer sortierbaren Liste enthält nur ein Doppeldreieck, das nach unten bzw. nach oben zeigt. Alle anderen Zeilen enthalten jeweils zwei Doppeldreiecke, von denen einer nach oben, der andere nach unten zeigt. Ein Klick auf ein Doppeldreieck verschiebt das jeweilige Element um eine Stelle nach oben bzw. unten, während das vormals darüber liegende Element die Position des verschobenen Elements einnimmt.
+ * Radiobuttons plus Winkelpfeile: In bestimmten Kontexten ist es mitunter erforderlich, mehrere Elemente nacheinander um eine große Anzahl von Positionen zu verschieben. Hierfür kann eine alternative Sortierfunktion angeboten werden. Bei dieser Lösung markiert man einen Eintrag mittels eines Radiobuttons (links vom Auf- und Zuklapp-Dreieck) und wählt durch Anklicken eines Icons (in Form eines gewinkelten Pfeils links des jeweiligen Radiobuttons) die Stelle, an welcher der gewählte Eintrag einsortiert werden soll.
+### nach Kriterium
+* Manchmal möchte man Listenelemente nach einem bestimmten kriterium sortieren (Name, Dateigröße, Datum usw.). Hierzu gelten folgende Regeln:
+ * Listenelemente können nur nach Kriterien sortiert werden, deren Werte an der Oberfläche sichtbar sind. Eine Liste von Dateien sollte also zum Beispiel nicht nach Datum sortiert werden können, wenn das Datum der Datei nicht auch eingeblendet ist.
+ * Das Sortieren erfolgt durch das Klicken auf eine Spaltenüberschrift, unterhalb der die Werte dieses Kriteriums für jedes Listenelement aufgeführt ist.
+ * Grundsätzlich soll es möglich sein, durch mehrfaches Klicken auf eine Spaltenüberschrift die Elementliste aufsteigend und absteigend nach dem jeweiligen Kriterium zu sortieren.
+### Probleme/Fragen beim Sortieren:
+* Wie sortiert man innerhalb hierarchischer Gliederungen die Elemente verschiedener Ebenen (Beispiel: Gruppen/Funktionen in Veranstaltungen)?
+* Wie geht man mit Paginierung um? Sortiert man die gesamte Liste oder nur die sichtbaren Elemente?
+
+
+# Meldungen
+
+Wo wird Rückmeldung angezeigt?
+* Nutzer befindet sich unten auf der Seite
+* da macht oben die Infomeldung anzuzeigen, keinen Sinn
+
+Rückfrage bei Löschen von Objekten, ob wirklich gelöscht werden soll?
+* derzeit unterschiedlich in Stud.IP
+
+
+
+## Sicherheitsabfragen
+* als modalen Dialog?
+* teilweise werden diese nicht als Dialog, sondern auf der Seite angezeigt
+
+
+Quelle: http://developer.android.com/design/patterns/confirming-acknowledging.html
+
+## Weiterführende Links
+* http://patternry.com/p=feedback-messages/
+* http://www.userfocus.co.uk/articles/errormessages.html
+* http://uxmag.com/articles/are-you-saying-no-when-you-could-be-saying-yes-in-your-web-forms
+
+Buch:
+Designed for Use Chapter 6 über Text Usability
diff --git a/docs/docs/visual-style-guide/fonts.md b/docs/docs/visual-style-guide/fonts.md
new file mode 100644
index 0000000..34f9b7f
--- /dev/null
+++ b/docs/docs/visual-style-guide/fonts.md
@@ -0,0 +1,14 @@
+---
+title: Schrift
+sidebar_label: Schrift
+---
+
+### Vorbemerkung
+
+In Stud.IP wird eine einheitliche Schriftart verwendet. Im Gegensatz zu früheren Versionen, in dem Betriebssystem und Browser die Schriftart bestimmt haben (in der Regel Arial oder Helvetica, serifenlose Schriftarten), liegt die Festlegung nun in den Stylesheets von Stud.IP. Das bietet eine Reihe von Vorteilen (festgelegte Formatierung Größen, tendenziell mehr wie in Print-Designs) aber auch Nachteile (unterschiedliche Darstellung, Lesbarkeit, Probleme mit Webfonts in bestimmten Browsern).
+
+### Schriftart Lato
+
+In Stud.IP kommt durchweg die Schriftart Lato zum Einsatz. Lato ist ein frei verfügbarer Google Font:
+
+https://www.google.com/fonts/specimen/Lato
diff --git a/docs/docs/visual-style-guide/formulare.md b/docs/docs/visual-style-guide/formulare.md
new file mode 100644
index 0000000..c5a7fe3
--- /dev/null
+++ b/docs/docs/visual-style-guide/formulare.md
@@ -0,0 +1,469 @@
+---
+title: Formulare
+sidebar_label: Formulare
+---
+
+
+Formulare sollen in Stud.IP gemäß LifTers014 einheitlich gestaltet werden. Ein Standard Formular wird wie folgt definiert:
+
+```php
+<form class="default" ...>
+ ...
+</form>
+```
+
+
+## Allgemein
+Lange Erklärungstexte am Anfang des Formulars sollten vermieden werden. Erklärungen können über Tooltips an den Elementen (siehe unten) oder ggf. Texte in der Hilfelasche realisiert werden.
+
+## Gruppierung der Formularfelder
+Formularfelder (oder auch Input-Elemente) sollen gruppiert werden, wenn sie inhaltlich oder funktional zusammenhängen, damit dieser Zusammenhang deutlich wird. Jede Gruppe sollte eine passende Überschrift haben.
+
+```php
+<form class="default" ...>
+ <fieldset>
+ <legend>Gruppenüberschrift 1</legend>
+ ...
+ </fieldset>
+ <fieldset>
+ <legend>Gruppenüberschrift 2</legend>
+ ...
+ </fieldset>
+</form>
+```
+
+Auch Formulare mit lediglich einer Gruppierung sind zulässig. In Dialogen wird eine einzelne Gruppierung jedoch entfernt!
+
+#### Ein-/Ausblenden von Gruppen
+
+Einzelne Gruppen können aus- bzw. eingeblendet werden, indem entweder dem `fieldset` (für eine spezielle Gruppe) oder dem gesamten Formular (für alle Gruppen innerhalb) die Klasse `collapsable` gegeben wird. Dadurch wird durch einen Klick auf die `legend` des `fieldset`s die Gruppe versteckt bzw. wieder angezeigt. Soll die Gruppe bei der initialen Darstellung ausgeblendet sein, muss das `fieldset` zusätzlich mit der Klasse `collapsed` ausgezeichnet werden.
+
+```php
+<form class="default" ...>
+ <fieldset>
+ <legend>Gruppenüberschrift 1</legend>
+ ...
+ </fieldset>
+ <fieldset class="collapsable collapsed">
+ <legend>Gruppenüberschrift 2</legend>
+ ...
+ </fieldset>
+</form>
+```
+
+### Labels
+Generell sollte analog zu LifTers010 das HTML-Markup `<label>` verwendet werden. Beispiel:
+
+```html
+<form class="default">
+ <fieldset>
+ <legend>Beschriftung</legend>
+ ...
+ <label>Eingabe A
+ <input name="eingabe_a" type="text" placeholder="Texteingabe A" required>
+ </label>
+ ...
+ </fieldset>
+ </form>
+```
+
+* Das erste Wort des Labels sollte mit einem großen Anfangsbuchstaben geschrieben werden.
+* Das Label sollte nicht mit einem Doppelpunkt abgeschlossen werden.
+
+Wording:
+* Es sollen aussagekräftige Labels gewählt werden.
+* Fachbegriffe sollen vermieden werden.
+* Keine ganzen Sätze.
+
+### Nicht änderbare / deaktivierte Eingabefelder
+
+Falls in einem Formular im aktuellen Kontext ein Feld nicht geändert werden darf, so muss das Attribut `disabled` an das Eingabefeld gehängt werden. Es ist nicht zulässig, einfach nur den Text OHNE Formularelement auszugeben!
+
+Beispiel aus lib/classes/StudipSemTreeViewAdmin.class.php
+```php
+<form class="default" ...>
+ <fieldset>
+ <legend><?= _("Bereich editieren") ?></legend>
+
+ <label>
+ <?= _("Name des Elements") ?>
+ <input type="text" name="edit_name"
+ <?= $this->tree->tree_data[$this->edit_item_id]['studip_object_id'])? 'disabled' : * ?>
+ value="<?= htmlReady($this->tree->tree_data[$this->edit_item_id]['name']) ?>">
+ </label>
+ ...
+ </fieldset>
+</form>
+```
+
+### Ausrichtung der Formularfelder
+
+Schmale Formularfelder dürfen in mehreren Spalten angeordnet werden. Bei schmaleren Anzeigen brechen diese Felder bei korrekter Anwendung automatisch um.
+
+Untereinander angeordnete Formularfelder sollten linksbündig angeordnet sein. Wenn mehrere Formularfelder eine logische Folge bilden oder aus anderen Gründen direkt zusammengehören, sollten Sie in einer Horizntalen Gruppierung (hgroup) angeordnet werden.
+
+#### Reguläre nebeneinader angeordnete Elemente
+
+Um Elemente bei passend großem Bildschirm nebeneinander anzuzeigen, werden diese in Spalten angeordnet. Es gibt insgesamt 6 Spalten und man Elementen eine Breite von 1 - 6 Spalten zuordnen. Dafür gibt es die Klassen col-1 bis col-5 - keine Angabe bedeutet dabei ganze Breite (enstpräche einem col-6).
+
+Diese Elemente werden dann bei schmaleren Anzeigen automatisch unterienander angezeigt.
+
+```php
+<form class="default" ...>
+ <label class="col-3">
+ Vorname
+ <input type="text" name="first-name">
+ </label>
+
+ <label class="col-3">
+ Nachname
+ <input type="text" name="last-name">
+ </label>
+</form>
+```
+
+
+#### Horizontale gruppiert angeordnete Elemente
+
+Um Elemente in einer Zeile horizontal zu gruppieren, benötigt es ein Wrapper-Element mit der Klasse `.hgroup`. Dieses Element nimmt die gleichen Größen wie die Elemente an und verteilt den Platz innerhalb von sich selbst erstmal gleich, aber die einzelnen Elemente können auch wiederum durch die bekannten Größenangaben beeinflusst werden.
+
+Die hgroup ist lediglich zulässig für kombinierte Eingabefelder, wie Telefonnummern, Datumsangaben etc. sowie RadioButtons mit sehr kurzen Labels (z.B. Geschlecht: m/w/kA, Schalter: ja/nein/kA, etc.). Es dürfen keine zu großen Felder und/oder zu lange Label-Texte bei der horizontalen Gruppierung verwendet werden!
+
+```php
+<form class="default" ...>
+
+ <!-- ... -->
+
+ <div>
+ <?= _('Geschlecht') ?>
+ </div>
+
+ <section class="hgroup">
+ <label>
+ <input type="radio" <? if (!$geschlecht) echo 'checked' ?> name="geschlecht" value="0">
+ <?= _("unbekannt") ?>
+ </label>
+
+ <label>
+ <input type="radio" <? if ($geschlecht == 1) echo "checked" ?> name="geschlecht" value="1">
+ <?= _("männlich") ?>
+ </label>
+
+ <label>
+ <input type="radio" name="geschlecht" <? if ($geschlecht == 2) echo "checked" ?> value="2">
+ <?= _("weiblich") ?>
+ </label>
+ </section>
+ <!-- ... -->
+</form>
+```
+
+Es gibt noch eine zweite Variante, die eingesetzt werden darf, wenn es sich bei dem Titel tatsächlich um das Label eines nachfolgenden Form-Elementes handelt. Beispiel aus der Nutzerverwaltung:
+
+```php
+<label for="inaktiv">
+ <?= _('inaktiv') ?>
+</label>
+
+<section class="hgroup">
+ <select name="inaktiv" class="size-s" id="inaktiv">
+ <? foreach(array('<=' => '>=', '=' => '=', '>' => '<', 'nie' =>_('nie')) as $i => $one): ?>
+ <option value="<?= htmlready($i) ?>" <?= ($request['inaktiv'][0] === $i) ? 'selected' : * ?>>
+ <?= htmlReady($one) ?>
+ </option>
+ <? endforeach; ?>
+ </select>
+
+ <label>
+ <input name="inaktiv_tage" type="number" id="inactive" value="0">
+ <?= _('Tage') ?>
+ </label>
+</section>
+```
+
+#### Kombinierte Variante mit col- und hgroup-Angaben
+
+Es ist ebenfalls möglich und zulässig, horizontal gruppierte Element in Spalten einzuteilen:
+
+```php
+<label class="col-3">
+ Telefonnummer
+ <section class="hgroup">
+ + <input type="text" size="3">
+ <input type="text" maxlength="5" class="no-hint" size="5"> /
+ <input type="text" maxlength="10" size="10">
+ </section>
+</label>
+
+<label class="col-3">
+ Fax
+ <section class="hgroup">
+ + <input type="text" size="3">
+ <input type="text" maxlength="5" class="no-hint" size="5"> /
+ <input type="text" maxlength="10" size="10">
+ </section>
+</label>
+```
+
+### Ausrichtung der Labels
+Die Labels sollen linksbündig und oberhalb der Eingabefelder angebracht sein. Dies erleichtert die Lesbarkeit der Beschriftungen und verdeutlicht den Zusammenhang zwischen den Feldbeschriftungen und den Eingabefeldern.
+
+Attach::formlabel2015.png
+
+Wenn der Platz in der Vertikalen beschränkt ist, sollen die Beschriftungen linksbündig und links neben den Formularfeldern angebracht sein. Dies erhält die Lesbarkeit und spart Platz in der Vertikalen. In diesem Fall sollten die Labels so gewählt werden, dass sie sich in ihrer Länge möglichst wenig unterscheiden, damit die Lücken zwischen den Labels und den Eingabefeldern nicht zu groß werden.
+
+Innerhalb eines Kontextes sollten die Beschriftungen einheitlich angeordnet werden.
+
+### Placeholder/Platzhalter
+Das placeholder-Attribut dient zum Befüllen von Eingabefeldern mit kurzen Hinweisen. Dieser Inhalt verschwindet, sobald ein Nutzer in das Eingabefeld klickt.
+* Placeholder sollten nicht als Alternative zum Label verwendet werden.
+* Placeholder sollten sparsam verwendet werden.
+
+Beispiel für ein korrekt verwendetes placeholder-Attribut:
+TODO: Screenshot
+
+
+Beispiel für ein **falsches** placeholder-Attribut:
+Attach::wronglabel.png
+
+
+## Art der Formularfelder
+Die Art der Eingabefelder soll so gewählt werden, dass man an ihr erkennen kann, welche Eingaben möglich sind. Ein Textfeld dient zur freien Eingabe von Zeichen ohne Beschränkungen (außer in der Zeichenanzahl). [Checkboxen](Checkboxen), [Radio Buttons](Visual-Style-Guide#RadioButtons) oder [Drop-Down Listen](DropDown) werden verwendet, um die Anzahl der Optionen einzuschränken oder für Einträge, wo sich Nutzer leicht vertippen.
+
+
+## Größe der Formularfelder
+Eingabefelder sollen groß genug sein, um typische Eingaben entgegen zunehmen, ohne dass man "über den rechten Rand hinausschreibt". Die Größe der Formularfelder soll so gewählt werden, dass sie deutlich machen, welche Eingaben dort möglich sind. Beispiel: Das Eingabefeld für die Veranstaltungsnummer sollte kürzer sein als das für den Veranstaltungstitel.
+
+Das Stud.IP-Stylesheet schlägt standardmäßig drei Größen vor (CSS-Klassen "size-s","size-m" und "size-l"):
+
+* size-s: 10em (gedacht für kurze Eingaben wie z.B. Zahlen)
+* size-m: 48em
+* size-l: 100%
+
+```php
+<form class="default" ...>
+...
+ <label>
+ Kurze Eingabe
+ <input type="text" class="size-s">
+ </label>
+
+ <label>
+ Mittlere Eingabe
+ <input type="text" class="size-m">
+ </label>
+
+ <label>
+ Längere Eingabe
+ <input type="text" class="size-l">
+ </label>
+...
+</form>
+```
+
+Attach::formsizes2015.png
+
+Die Voreinstellung ist "size-m". Ausnahme: Für die Input-Typen "number" und "date" ist die Voreinstellung "size-s".
+```php
+<form class="default narrow" ...>
+ ...
+</form>
+```
+
+### Schmale Formulare
+
+Manchmal ist es notwendig, ein Formular standardmäßig besonders platzsparend anzubieten (siehe z.B. Admin > Standort > Veranstaltungshierarchie).
+Dafür kann dem Formular die Klasse "narrow" hinzugefügt werden. Dies sorgt dafür, dass die einzelnen Formularelemente etwas enger zusammenrücken, um ein frühzeitiges umbrechen zu vermeiden.
+
+Attach::narrow_form.png
+
+## Kennzeichnung von Pflichtfeldern
+
+```php
+<form class="default" ...>
+ <fieldset>
+ <legend>Beschriftung</legend>
+ ...
+ <label>
+ <span class="required">Eingabe A</span>
+ <?= tooltipIcon(_('Bitte geben Sie hier nur eine Zahl ein')) ?>
+ <input type="number">
+ </label>
+ ...
+ </fieldset>
+ </form>
+```
+
+
+Pflichtfelder müssen mit einem hochgestellten roten Stern rechts neben der Feldbeschriftung gekennzeichnet werden. Die kann in einem Label mittels `<span class="required">` im Quelltext umgesetzt werden.
+
+### Hinweistexte zu den Formularfeldern [#Hinweistexte](#Hinweistexte)
+
+Da die Beschriftung eines Formularfelds möglichst kurz sein sollte, ist es möglich, dass weitere Informationen oder erläuternde Hinweise zum entsprechenden Feld nötig sind. Ein erforderlicher Hinweis- oder Beschreibungstext zu einem Formularfeld wird mittels Tooltip realisiert. Der Tooltip wird über die vorhandene Logik `<?= tooltipIcon(_('...'))?>` rechts neben dem Label und ggf. hinter der Kennzeichnung eines Pflichtfeld positioniert.
+
+Attach:formtooltip2015.png
+
+
+## Formatvorgaben und Eingabevalidierung
+Wenn Eingaben nur in einem bestimmten Format erfolgen dürfen, soll dies kenntlich gemacht werden, entweder durch
+* entsprechende Wahl bzw. Gestaltung der Formularfelder,
+* eine "intelligente" Interpretation der Eingaben (z.B. Erkennung von 15 oder 1500 als Uhrzeit 15:00 Uhr) oder
+* Hinweise beim Eingabefeld [siehe Hinweistexte](#Hinweistexte).
+* Verwendung entsprechender Input-Types (siehe [Eingabevalidierung](Howto/Eingabevalidierung))
+
+Die Eingabevalidierung soll, wenn möglich, direkt nach Verlassen des jeweiligen Eingabefeldes erfolgen. Zu jedem nicht ausgefüllten Pflichtfeld bzw. zu jedem sonstwie falsch ausgefüllten Eingabefeld soll der Korrekturhinweis direkt bei dem jeweiligen Eingabefeld erfolgen, so dass die Aufmerksamkeit des Benutzers direkt auf die noch zu vorzunehmenden bzw. zu korrigierenden Eingaben gelenkt werden.
+
+Weitere Informationen: [Eingabevalidierung](Howto/Eingabevalidierung)
+
+## Buttons
+Der Button zum Abschicken/Speichern/Übernehmen der eingegebenen Daten ("primäre Aktion") sollte linksbündig mit den Formularfeldern abschließen und sich direkt unterhalb des Formulars im `<footer>`-Element befinden. Damit wird deutlich, welche Daten durch einen Klick auf diesen Button übernommen werden.
+
+Ein Button zum Abbrechen oder Zurücksetzen ("sekundäre Aktion") soll vermieden werden. Wenn er erforderlich ist, soll er sich visuell von dem Button für die primäre Aktion unterscheiden.
+
+```php
+<form class="default" ...>
+...
+ <footer>
+ <?= \Studip\Button::createAccept(_("Speichern")) ?>
+ <?= \Studip\Button::createCancel(_("Abbrechen")) ?>
+ </footer>
+</form>
+```
+
+
+
+Attach:formfooter2015.png
+
+
+
+
+* TODO: Genauere Vorgaben für die Gestaltung von Buttons für sekundäre Aktionen formulieren
+
+#### Ausnahme: Buttons bei Wizards
+* wo sollten die Buttons für "zurück" und "weiter" bei mehrseitigen Formularen platziert werden
+ ** zentriert ausgerichtet, wie groß der Abstand zwischen beiden Buttons?
+
+Bei längeren Formularen (die über eine Bildschirmseite gehen): Buttons "verdoppeln", also oben und unten auf der
+Seite anzeigen z. B. "zurück" und "weiter" Buttons
+
+* http://patternry.com/p=multiple-page-wizard/
+* weitere Recherche zu Buttons bei Wizards
+ ** Attach:labelsonform.pdf
+ ** Quelle: http://de.slideshare.net/cjforms/labels-and-buttons-on-forms/
+
+### Weiterführende Informationen
+
+#### Allgemein
+
+* Cheat Sheet For Designing Web Forms http://uxdesign.smashingmagazine.com/2011/10/07/free-download-cheat-sheet-for-designing-web-forms/ Attach:formsheet.pdf
+* http://uxdesign.smashingmagazine.com/2011/11/08/extensive-guide-web-form-usability/
+* http://www.formsthatwork.com/Articles
+* http://www.slideshare.net/cjforms/labels-and-buttons-on-forms
+* [Paper](http://www.intechopen.com/download/pdf/10814) "Simple but Crucial User Interfaces in the World Wide Web: Introducing 20 Guidelines for Usable Web Form Design"
+
+#### Placeholder
+* http://mentalized.net/journal/2010/08/05/dont_use_placeholder_text_as_labels/
+* http://dev.w3.org/html5/spec/single-page.html#the-placeholder-attribute
+* http://laurakalbag.com/labels-in-input-fields-arent-such-a-good-idea/
+
+
+## Checkboxen
+
+### Verwendung
+Checkboxen werden verwendet, um Optionen zu aktivieren bzw. zu deaktivieren.
+
+### Aussehen
+* Checkboxen sollten möglichst untereinander angeordnet werden. Dadurch können Sie einfacher gelesen werden.
+* Die Bezeichnung ist rechts vom Kästchen zu platzieren.
+* Kästchen und Bezeichnung sind linksbündig untereinander anzuordnen.
+
+### Beschriftung
+Negative Beschriftungen sollten vermeiden werden:
+* markierte Checkboxen aktivieren Einstellungen und deaktivieren diese nicht
+
+```html
+ <form ... >
+ <fieldset>
+ <legend>Beschriftung</legend>
+
+ <fieldset>
+ <legend>Checkboxengruppe</legend>
+ <input class="studip_checkbox" id="cb1" type="checkbox" name="cb" value="1">
+ <label for="cb1">Antwortmöglichkeit 1</label>
+ <input class="studip_checkbox" id="cb2" type="checkbox" name="cb" value="2">
+ <label for="cb2">Antwortmöglichkeit 2</label>
+ <input class="studip_checkbox" id="cb3" type="checkbox" name="cb" value="3">
+ <label for="cb3">Antwortmöglichkeit 3</label>
+ </fieldset>
+ ...
+ </fieldset>
+ </form>
+```
+### Reihenfolge der Checkboxen
+Wenn mehrere Checkboxen auf einer Seite vorhanden sind, sollten diese in einer logischen Reihenfolge aufgelistet sein, z. B. die Optionen zuerst, die am häufigsten verwendet werden.
+
+## Radio Buttons [#RadioButton](#RadioButton)
+
+### Verwendung
+Mit Hilfe von Radio Buttons können Nutzer genau eine Option von sich gegenseitig ausschließenden Alternativen wählen, z. B. E-Mail als Text oder in HTML versenden.
+
+Wenn es mehr als vier/sechs Optionen gibt, ist eine [Drop-Down-Liste](Visual-Style-Guide#DropDown) die bessere Wahl.
+
+### Verhalten
+Wenn möglich, sollte eine sinnvolle Default-Option vorausgewählt sein.
+
+```html
+<form class="default" ... >
+ <fieldset>
+ <legend>Beschriftung</legend>
+
+ <fieldset>
+ <legend>Checkboxengruppe</legend>
+ <input class="studip_checkbox" id="cb1" type="checkbox" name="cb" value="1">
+ <label for="cb1">Antwortmöglichkeit 1</label>
+ <input class="studip_checkbox" id="cb2" type="checkbox" name="cb" value="2">
+ <label for="cb2">Antwortmöglichkeit 2</label>
+ <input class="studip_checkbox" id="cb3" type="checkbox" name="cb" value="3">
+ <label for="cb3">Antwortmöglichkeit 3</label>
+ </fieldset>
+ ...
+ </fieldset>
+ </form>
+```
+
+#### Aussehen
+Radio Buttons sollten möglichst untereinander angeordnet werden. Dadurch können Sie leichter überflogen werden.
+Die Bezeichnung sollte rechts daneben sein.
+
+## Drop-Down Listen [#DropDown](#DropDown)
+
+Mit Hilfe von Drop-Down Listen können Nutzer genau eine Option aus zwei oder mehreren sich gegenseitig ausschließenden Optionen wählen. Sie werden anstelle von [Radio Buttons](Visual-Style-Guide#RadioButtons) für lange Listen mit Optionen verwendet.
+
+### Sortierung der Optionen
+Die Optionen sollten aufgabenlogisch oder natürlich angeordnet werden z. B. bei Wochentagen zuerst Montag, Dienstag. Falls es keine logisch sinnvolle Reihenfolge gibt, sollten die Optionen alphabetisch (bzw. alphanumerisch) angeordnet werden.
+
+http://uxmovement.com/forms/stop-misusing-select-menus
+
+### Verhalten
+Drop-Down Listen sollten möglichst einen voreingestellten Wert haben.
+
+## List Boxen
+### Verwendung
+List Boxen können als Alternative zu einer Reihe von [Radio Buttons](Visual-Style-Guide#RadioButton), die es ermöglichen, genau eine Option aus einer Reihe von sich gegenseitig ausschließenden Optionen zu wählen. Oder als eine Alternative zu [Checkboxen](Checkboxen) dienen, die es ermöglichen, eine beliebige Anzahl von Auswahlmöglichkeiten, aus einer Liste von Optionen auszuwählen. Sie benötigen weniger Platz auf dem Bildschirm als eine Liste von Radio-Buttons oder Checkboxen.
+
+List Boxen sollten nur sehr sparsam verwendet werden.
+
+## Datumseingaben
+* Veranstaltungstermin anlegen/bearbeiten
+* Termin im Terminkalender anlegen
+* Zeitbereich für Export im Terminkalender definieren
+* Regelmäßige Zeit anlegen/bearbeiten
+* Anzuzeigendes Datum im Belegungsplan definieren
+* Ressourcenbelegung eintragen/bearbeiten
+* Gültigkeitsdauer von
+ * News
+ * Votings
+ * Evaluationen
+* Anmeldezeitraum für Veranstaltungen definieren
+* Eigene "Veranstaltung" in Stundenplan eintragen
+* generische Datenfelder vom Typ "Datum"?
diff --git a/docs/docs/visual-style-guide/icons.md b/docs/docs/visual-style-guide/icons.md
new file mode 100644
index 0000000..2b2cfea
--- /dev/null
+++ b/docs/docs/visual-style-guide/icons.md
@@ -0,0 +1,410 @@
+---
+title: Icons & Grafiken
+sidebar_label: Icons & Grafiken
+---
+
+Seit der Version 2.0 bringt Stud.IP ein einheitliches Icon-Set mit. Das Set zeichnet sich durch eine klare und einheitliche Gestaltung, nach Aufgaben und Einsatzbereichen getrennte Farb-Sets, barrierearme Bedienung durch klare Formen/optische Zusätze für bestimme Aufgaben und eine Reihe weiterer Merkmale, die die Stud.IP-Formensprache vereinheitlichen, aus. Des weiteren liegen alle Icons bereits als Vektorgrafiken vor, so dass eine Verwendung in anderen Kontexten (zB. Print) und eine Veränderung der Icon-Größe in Stud.IP (etwa für eine verbesserte Touch-Bedienung) zukünftig möglich ist.
+Das Iconset findet grundsätzlich für alle grafischen Schaltflächen, Markierungen, Objekt-Darstellungen usw. Anwendung. Als einzige Ausnahme davon existieren in Stud.IP weiterhin die Textbuttons und einige wenige Sonderformen oder nicht-iconartige Grafiklelemente (zB. Linien oder Trenner).
+
+
+Generell werden alle Grafiken im `assets`-Ordner `images` im `public`-Ordner gespeichert. Diese wurden in 2.0 neu strukturiert und aufgeräumt. Somit ergibt sich dort folgende Struktur:
+
+| Pfad | Beschreibung |
+| ---- | ---- |
+| `public/assets/images/calendar` | Hier sind alle Hintergrundgrafiken drin, die für den Kalender und Stundenplan benötigt werden. |
+| `public/assets/images/crowns` | Die Kronen, die ein Benutzer als Auszeichnung bekommen kann |
+| `public/assets/images/header` | `deprecated` (Diese Icons werden noch gelöscht) |
+| `public/assets/images/icons` | Der neue Ordner für alle Icons, ausführliche Informationen siehe unten. |
+| `public/assets/images/infobox` | Alle Bilder mit abgerundeter Ecke in Schwarz-weiß für die Infoboxen |
+| `public/assets/images/languages` | Länder-Icons für die vorhanden Sprachen |
+| `public/assets/images/locale` | Hier sind alle sprachabhängigen Icons |
+| `public/assets/images/logos` | Hier liegen alle Logos, die irgendwo in Stud.IP verwendet werden |
+| `public/assets/images/vendor` | Hier werden Grafiken abgelegt, die zu anderen Paketen, Frameworks etc gehören und nicht von uns erstellt wurden (wie z.B. jQuery-UI) |
+
+### Gestaltung
+
+Die Icon-Landschaft wurde deutlich entschlackt, moderner und effizienter gestaltet. Skalierbarkeit und eine einfache, klare Formensprache standen bei der Entwicklung im Mittelpunkt. Das neue Iconset ist funktional und selbsterklärend. So wird die Übersichtlichkeit der Funktionen und die intuitive Bedienung gefördert. Es gibt nun für alle Größen und Einsatzgebiete eine einheitliche Optik der neuen Icons. Die Verwendung von festen Grundformen zusammen mit förmlich und farblich abgegrenzten Zusätzen, die Funktionen und Zustände markieren, gewährleistet eine barrierearme Nutzbarkeit bei hohem Wiedererkennungseffekt.
+
+Icons in Studip 2.0 sind nach einem festgelegten Raster gestaltet und minimalistisch in Farbe und Form. Sie sind grundsätzlich monochrom in festgelegten Farben gehalten. Linienstärke und Farbfüllungen werden so eingesetzt, dass die Icons ein einheitliches optisches Gewicht erhalten. Diese Regeln sollen künftig auf alle in Stud.IP verwendeten Icons übertragen werden.
+
+Sicher gibt es zusätzlich zur normalen Projektentwicklung viele Plugins und Erweiterungen, bei denen einen Bedarf gibt, bestehende Icons anzupassen oder neue zu erstellen. Die dafür notwendigen Arbeiten übernimmt der Stud.IP e.V., der für die Entwicklung in Budget in gewissem Umfang bereithält. Koordiniert die Entwicklung über den Vorstand des Stud.IP e.V.
+
+### Icon-Rollen
+
+Ab der Version 3.4 werden Icons über die Icon-API angesprochen. Bisher wurden Icons wie Dateinamen eingebunden. Damit war die verwendete Farbe hartkodiert. Wenn in Stud.IP-Installationen Anpassungen an das Farbschema der Hochschule vorgenommen wurden, musste daher der Code geändert bzw. unschöne "Hacks" vorgenommen werden.
+
+Ab Stud.IP v3.4 werden Icons nicht mehr mit ihrer Farbe referenziert, sondern mit der Rolle, die sie übernehmen wollen.
+Ein Beispiel: Bisher wurden alle Icons, die als bzw. in einem Link angezeigt wurden, im Code mit der Farbe Blau hartkodiert: `Assets::img('icons/blue/seminar')` Wollte eine Hochschule alle Link-artigen Icons lieber in der Farbe
+Grün darstellen, mussten dafür grüne Icons in das Verzeichnis "blue" gelegt werden (Was aber auch nicht immer funktioniert,
+wenn z.B. die Hintergrundfarbe dann dieselbe wie die Icon-Farbe ist.) oder alle entsprechenden Vorkommnisse von `blue` im Code durch `green` ersetzen.
+
+Mit der neuen Icon-API wird nun die Rolle hartkodiert. Die globale Zuordnung von Rollen zu Farben übernimmt dann die entsprechende Übersetzung.
+
+### Rollen
+
+Derzeit (Stud.IP v3.4) findet man die Zuordnung von Rollen zu Farben
+in der Klasse "Icon" (`lib/classes/Icon.class.php`):
+
+| Rolle | Farbe | Bedeutung |
+| ---- | ---- | ---- |
+| `Icon::ROLE_INFO` | black | Alte Beschreibung: *Diese Icons werden ausschließlich in den Infoboxen verwendet und sind nie klickbar. Sie erläutern grafisch die Aktionen, die die Infobox anbietet. So können hier Aktionen mit dem zugehörigen Icon gezeigt werden, Informationen mit einem "I" illustriert werden oder Verweise auf andere Systembereiche mit den zu diesen Bereichen passenden Icons ergänzt werden.* |
+| `Icon::ROLE_CLICKABLE` | %blue%blue | Alte Beschreibung: *Die blauen Icons sind der Standard für alle klickbaren Icons, unabhängig davon, in welchem Kontext sie verwendet werden (Ausnahme: Übersicht "Meine Veranstaltungen"). Insbesondere freistehende Aktionen sollten immer neben dem Linktext ein solches Icon zeigen. * |
+| `Icon::ROLE_ACCEPT` | %green%green | Alte Beschreibung: *Grün kommt nur in dem Fall, dass der Bestätigungs-Haken gezeigt wird, zum Einsatz.* |
+| `Icon::ROLE_STATUS_GREEN` | %green%green | Alte Beschreibung: *Grün kommt nur in dem Fall, dass der Bestätigungs-Haken gezeigt wird, zum Einsatz.* |
+| `Icon::ROLE_INACTIVE` | %grey%grey | Alte Beschreibung: *Diese Variante kommt zum Einsatz, wenn Icons nicht klickbar sind und nicht innerhalb von Infoboxen eingesetzt werden. Sie drücken auch oft einen Status aus und werden für alle Objekte wie News, Votings oder Dateien verwendet, um ein solches Objekt zu klassifizieren. Eine Ausnahme bildet "Meine Veranstaltungen". Auch hier drücken die grauen Icons einen Zustand aus ("nur bekannte Objekte eines Types") aber können trotzdem geklickt werden, da von hier aus auch die jeweiligen Bereiche direkt angesprungen werden können. Dieser Sonderfall gilt jedoch nur für "Meine Veranstaltungen"* |
+| `Icon::ROLE_NAVIGATION` | %ltblue%lightblue | |
+| `Icon::ROLE_NEW` | red | Alte Beschreibung: *Die Farbe Rot dient dazu, Neues darzustellen oder hervorzuheben. Rote Icons kommen auf "Meine Veranstaltungen" zum Einsatz, wenn einer der Bereiche einer Veranstaltung für den Nutzer neue Inhalte führt. Aus Gründen der barrierearmen Bedienung reicht die rote Färbung allein nicht aus, es muss immer auch der Zusatz "neu" verwenden werden, um dem Icon auch eine eindeutige Form zu geben, es sei denn, die Kombination von Farbe und Form des Icons selbst ist eindeutig (etwa ein rotes X).* |
+| `Icon::ROLE_ATTENTION` | red | Alte Beschreibung: *Die Farbe Rot dient dazu, Neues darzustellen oder hervorzuheben. Rote Icons kommen auf "Meine Veranstaltungen" zum Einsatz, wenn einer der Bereiche einer Veranstaltung für den Nutzer neue Inhalte führt. Aus Gründen der barrierearmen Bedienung reicht die rote Färbung allein nicht aus, es muss immer auch der Zusatz "neu" verwenden werden, um dem Icon auch eine eindeutige Form zu geben, es sei denn, die Kombination von Farbe und Form des Icons selbst ist eindeutig (etwa ein rotes X).* |
+| `Icon::ROLE_STATUS_RED` | red | Alte Beschreibung: *Die Farbe Rot dient dazu, Neues darzustellen oder hervorzuheben. Rote Icons kommen auf "Meine Veranstaltungen" zum Einsatz, wenn einer der Bereiche einer Veranstaltung für den Nutzer neue Inhalte führt. Aus Gründen der barrierearmen Bedienung reicht die rote Färbung allein nicht aus, es muss immer auch der Zusatz "neu" verwenden werden, um dem Icon auch eine eindeutige Form zu geben, es sei denn, die Kombination von Farbe und Form des Icons selbst ist eindeutig (etwa ein rotes X).* |
+| `Icon::ROLE_INFO_ALT` | %bgcolor=black white%white | Alte Beschreibung: *Die weiße Variante wird immer dann verwendet, wenn Icons innerhalb einer dunklen Umgebung (in der Regel die Kopfzeile von frei schwebenden Fenstern mit dunkelblauer Zeile) vewendet werden. Auch graue Tabellenköpfe erhalten ggf. weiße Icons. In der Regel sind diese nicht klickbar. Das weiße Icon kann auf weißem Hintergrund nicht gesehen werden.* |
+| `Icon::ROLE_SORT` | %bgcolor=black yellow%yellow | Alte Beschreibung: *Gelbe Icons werden ausschließlich zum Verschieben von Objekten benutzt. Daher existieren in diesem Set auch nur Dreiecke und Pfeile in allen Varianten.* |
+| `Icon::ROLE_STATUS_YELLOW` | %bgcolor=black yellow%yellow | Alte Beschreibung: *Gelbe Icons werden ausschließlich zum Verschieben von Objekten benutzt. Daher existieren in diesem Set auch nur Dreiecke und Pfeile in allen Varianten.* |
+
+
+### Zusätze
+
+Es existieren eine Reihe von grafischen Zusätzen, die vielfältig eingesetzt werden können, um Aktionen, die hinter Icons liegen oder auch Zustände zu visualisieren. In der Regel werden Zusätze immer in rot dargestellt. Eine Ausnahme bildet der gelbe Zusatz „Verschieben“.
+
+Zusätze an Icons werden ab Stud.IP v3.4 über die Icon-API referenziert. Möchte man zB ein `seminar`-Icon als Link mit dem Zusatz `add` (also Hinzufügen) einbauen: `Icon::create('seminar+add')`
+
+| Status | Bild |Beschreibung
+| ---- | ---- | ---- |
+| `accept` | ![](https://develop.studip.de/studip/assets/images/icons/blue/accept/seminar.svg) | **Akzeptieren**: Der Haken drückt aus, dass hier eine Bestätigung im Kontext des Objekts dargestellt wird. |
+| `add` | ![](https://develop.studip.de/studip/assets/images/icons/blue/add/seminar.svg) | **Hinzufügen**: Das Plus-Zeichen drückt aus, dass hier ein neues Objekt erzeugt werden kann. Das Anlegen eines Objektes oder der Sprung auf einen Bereich, in dem das Anlegen möglich ist, wird mit diesem Zusatz belegt. Er kann an blauen Icons mit Klick oder schwarzen und grauen Icons verwendet werden.|
+| `decline` | ![](http://develop.studip.de/studip/assets/images/icons/blue/icons/grey/decline/trash.svg) | **Aktion gesperrt**: Zuweilen werden Aktionen als "nicht möglich" dargestellt. In diesem Fall erhalten die Aktions-Icons ein X als Zusatz. |
+| `edit` | ![](https://develop.studip.de/studip/assets/images/icons/blue/edit/seminar.svg) | **Bearbeiten**: Der Stift an einem Objekt drückt aus, das mit einem Klick auf dies Icon ein Objekt bearbeitet werden kann. |
+| `export` | ![](https://develop.studip.de/studip/assets/images/icons/blue/export/seminar.svg) | **Exportieren**: Über dieses Icon werden ein oder mehrere Objekte des entsprechenden Types exportiert (z. B. als CSV-Datei)|
+| `move_right` | ![](https://develop.studip.de/studip/assets/images/icons/blue/move_right/mail.svg) | **Verschieben**: Um ein Objekt zu verschieben, gibt es den Zusatz eines Pfeils. Bis zur Version 2.4 waren diese Zusätze gelb, seit der Version 2.5 sind alle Zusätze in rot zu halten.
+| `new` | ![](http://develop.studip.de/studip/assets/images/icons/blue/icons/red/new/seminar.svg) | **Neu**: Das Sternchen drückt einen neuen Inhalt aus. Das Sternchen wird (außer in der Kopfzeile) mit einem roten Icon kombiniert.|
+| `remove` | ![](https://develop.studip.de/studip/assets/images/icons/blue/remove/seminar.svg) | **Löschen/Entfernen**: Mit einem Minus wird die Möglichkeit, das entsprechende Objekt zu löschen, markiert.|
+| `visibility-visible` | ![](https://develop.studip.de/studip/assets/images/icons/blue/visibility-visible/seminar.svg) | **Sichtbar**: Ein Objekt wird bei Klick auf dieses Icon sichtbar geschaltet. |
+| `visibility-invisible` | ![](https://develop.studip.de/studip/assets/images/icons/blue/visibility-invisible/seminar.svg) | **Unsichtbar**: Ein Objekt wird bei Klick auf dieses Icon unsichtbar geschaltet. |
+
+
+Das Icon-Set enthält im Bausatz noch weitere Zusätze, die aktuell jedoch nicht verwendet werden. So finden sich Pfeile in alle vier Richtungen, Pause-Zeichen, Bestätigen-Zeichen (als Gegenstück zum "X"), Minus-Zeichen (als Gegenstück zum Hinzufügen) und Aktualisieren.
+
+### Größen
+
+Icon-Größen können über die Render-Methoden der Icon-API angegeben werden. Inzwischen werden Icons als frei skalierbare SVG ausgeliefert.
+Seit der Version 5.0 wird die Größe im Icon nicht mehr mitgegeben (vormals 16 * 16 Pixel, außer anders angegeben).
+
+### Verzeichnisstruktur
+
+War früher die modulare Verzeichnisstruktur wichtig, verbirgt die Icon-Klasse nun diese Implementationsdetails. Bei Verwendung der Icon-API kommt man damit nicht mehr in Berührung.
+
+Historisch lagen die Icons in etwa dieser Verzeichnisstruktur:
+`icons/<Größe>/<Farbe>/<Zuatz>/<Name des Icons>.png`
+
+### Liste der verfügbaren Icons
+
+#### Stammicons
+
+Für alle in Stud.IP vorhandenen Objekte existieren Stammicons, von denen alle weiteren Formen oder Varianten logisch abgeleitet werden. Gegenwärtig existieren folgende Stammicons:
+
+(In vielen Fällen existieren sowohl gefüllte und transparente bzw. invertierte Varianten zu einem Stammicon. Hier ist in der Regel die normale Version und nicht die Alternative einzusetzen.)
+
+| Bild | Lizenz | Beschreibung
+| ---- | ---- | ---- |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/60a.svg) | 60a | **Lizenz nach §60a** Dokument-Weitergabe im Rahmen der §60a Lizenz (aktuell)|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/cc.svg) | CC | **Lizenz nach CC** Dokument-Weitergabe im Rahmen von CC |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/license.svg) | license | **Lizenz allgemein** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/own-license.svg) | own-license | **Eigene Lizenz** Dokument-Weitergabe im Rahmen einer eigenen Lizenz/selbst erstellt |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/public-domain.svg) | public-domain | **Freie Lizenz** Dokument-Weitergabe im Rahmen einer freien Lizenz|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/accept.svg) | accept | **Akzeptieren/akzeptiert** Dieses Symbol ist die Grundform für positive Rückmeldungen an den Nutzer. |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/action.svg) | action | **Aktionsmenu** Icon zur Initiierung bzw. Verankerung des Aktionsmenu|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/activity.svg) | activity | **Activity-Stream** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/no-activity.svg) | no-activity | **keine Aktivität im Activity-Stream** leerer Activity-Stream |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/add.svg) | add | **Hinzufügen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/add-circle.svg) | add-circle | **Hinzufügen** für Popover |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/admin.svg) | admin | **Administration** Alle Administrationen des Systems |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/archive.svg) | archive | **Archiv** für alles, was mit dem Archivieren zu tun hat |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/archive2.svg) | archive2 | **Archiv Alternative** Alternative, die auch für Ordner o.ä. verwendet werden kann |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/archive3.svg) | archive3 | **Archiv Alternative** Alternative, die auch für Ordner o.ä. verwendet werden kann |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/assessment.svg) | assessment | **Prüfungen/Asssessments** allgemeines Icon für Prüfungen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/audio.svg) | audio | **Audio-Element** allgemeines Icon für Audio-Inhalt |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/audio3.svg) | audio3 | **Audio-Element** allgemeines Icon für Audio-Inhalt, Variante für Audio-Medienobjekt |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/billboard.svg) | billboard | **Schwarzes Brett** Icon für schwarze Bretter in Stud.IP |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-accordion.svg) | block-accordion | **Block-Icon für Akkordeon** Icon für den Courseware-Block Akkorden|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-canvas.svg) | block-canvas | **Block-Icon für Canvas** Icon für den Courseware-Block Canvas|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-comparison.svg) | block-comparison | **Block-Icon für Comparison** Icon für den Courseware-Block Bildvergleich|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-eyecatcher.svg) | block-eyecatcher | **Block-Icon für Eye-catcher** Icon für den Courseware-Block Blickfang|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-eyecatcher2.svg) | block-eyecatcher2 | **Block-Icon für Eye-catcher** Alternativ-Icon für den Courseware-Block Blickfang|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-gallery.svg) | block-gallery | **Block-Icon für Galerie** Icon für den Courseware-Block Bildergalerie|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-gallery2.svg) | block-gallery | **Block-Icon für Galerie** Alternativ-Icon für den Courseware-Block Bildergalerie|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-imagemap.svg) | block-imagemap | **Block-Icon für Imagemap** Icon für den Courseware-Block Imagemap|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-imagemap2.svg) | block-imagemap2 | **Block-Icon für Canvas** Alternativ-Icon für den Courseware-Block Imagemap |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-tabs.svg) | block-tabs | **Block-Icon für Tabs** Icon für den Courseware-Block Tabs |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/block-typewriter.svg) | block-tabs | **Block-Icon für Schreibmaschinen** Icon für den Courseware-Block Schreibmaschine |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/blubber.svg) | blubber | **Blubber** Icon für die Blubber-Funktion |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/brainstorm.svg) | brainstorm | **Brainstorm** Icon für das Brainstorm-Plugin |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/bus.svg) | bus | **Bus** Icon für Navigationsfunktionen, zB. in Campus-Apps |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/campusnavi.svg) | campusnavi | **Campus-Navi** Icon für Navigationsfunktionen allgemein, zb. in Campus-Apps |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/category.svg) | category | **Kategorie** allgemeines Icon für Kategorien |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/cellphone.svg) | cellphone | **Telefon/Handy** Telefonnummer, Smartphone usw. |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/chat.svg) | chat | **Chat** Chat/Forum/Messenger) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/check-circle.svg) | check-circle | **Akzeptieren/akzeptiert** für Popover |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/checkbox-checked.svg) | checkbox-checked | **markierte Checkbox** Checkbox in Form eines Icons, markiert |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/checkbox-unchecked.svg) | checkbox-unchecked | **unmarkierte Checkbox** Checkbox in Form eines Icons, unmarkiert |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/checkbox-indeterminate.svg) | checkbox-indeterminate | **uneindeutige Checkbox** Checkbox in Form eines Icons, uneindeutig (für Mehrfachselektion) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/radiobutton-checked.svg) | radiobutton-checked | **markierter Radiobutton** Radiobutton in Form eines Icons, markiert |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/radiobutton-unchecked.svg) | radiobutton-unchecked| **unmarkierter Radiobuttom** Radiobutton in Form eines Icons, unmarkiert |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/classbook.svg) | classbook | **Klassenbuch** Klassenbuch |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/clipboard.svg) | clipboard | **Zwischenablage** Kopieren in Zwischenablage |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/cloud.svg) | cloud | **Cloud-Service** generisches Icon für Cloudservices |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/code.svg) | code | **Programmcode** generisches Icon für Programmcode |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/code-qr.svg) | code-qr | **QR-Code** QR-Code |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/computer.svg) | computer | **Computer** allgemeines Computer-Icon |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/comment.svg) | comment | **Kommentar** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/comment2.svg) | comment2 | **Kommentar** Alternative für Kommentare|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/community.svg) | community | **Community** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/computer.svg) | computer | **Computer** allgemeines Icon für Computer (analog zu Telefon/Smartphone)|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/consultation.svg) | consultation | **Sprechstunden** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/content2.svg) | content | **Inhalte** allgemeines Icon für Inhalte|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/courseware.svg) | courseware | **Basis-Icon Courseware** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/crop.svg) | crop | **Beschneiden** Beschneiden von Bildern (zB. Avatar-Bilder) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/crown.svg) | crown | **Krone** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/date.svg) | date | **Termin** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/date-single.svg) | date-single | **Einzeltermin** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/date-cycle.svg) | date-cycle | **regelmäßiger Termin** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/date-block.svg) | date-block | **Blocktermin** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/decline.svg) | decline | **ablehnen** Dieses Symbol ist die Grundform für negative Rückmeldungen an den Nutzer. |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/decline-circle.svg) | decline-circle | **ablehnen** Ablehnen-Variante im Kreis|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/dialog-cards.svg) | dialog-cards | **Visitenkarten** Icon für Visitenkarten/Adressen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/doctoral-cap.svg) | doctoral-cap | **Prüfungen/Abschlüsse** allgemeines Icon für Prüfungen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/doit.svg) | doit | **Do.IT**Do.IT-Plugin und andere aufgabenbezogene Funktionen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/door-enter.svg) | door-enter | **Login/Betreten** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/door-leave.svg) | door-leave | **Logout/Verlassen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/download.svg) | download | **Download** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/dropbox.svg) | dropbox | **Cloud-Service Dropbox**Alternative |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/edit.svg) | edit | **Editieren** allgemeines Editieren-Icon |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/elmo.svg) | elmo | **Elmo** Icon für Elmo-Plugin |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/eportfolio.svg) | eportfolio | **ePortfolio-Icon** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/euro.svg) | euro | **Euro** Währungszeichen/Geld |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/evaluation.svg) | evaluation | **Evaluation** generelles Icon für Evaluationen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/exclaim.svg) | exclaim | **Hinweis** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/exclaim-circle.svg) | exclaim-circle | **Hinweis** für Popover |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/export.svg) | export | **Export** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/facebook.svg) | facebook | **Facebook** Facebook-Anbindung oder Verknüpfung * |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/favorite.svg) | favorite | **Favorit/Like** Favoriten-Icon * |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file.svg) | file | **Dokument** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/files.svg) | files | **Dokumente/Dateibereich** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-archive.svg) | file-archive | **Zip-Datei** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-audio.svg) | file-audio | **Audio-Datei** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-audio2.svg) | file-audio2 | **Audio-Datei** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-sound.svg) | file-sound | **Audio-Datei** Alternative, zB. für laute Audiodateien|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-pic.svg) | file-pic | **Bilddatei** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-pic2.svg) | file-pic2 | **Bilddatei** Alternative|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-pdf.svg) | file-pdf | **PDF-Datei** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-presentation.svg) | file-presentation | **Präsentation-Datei** generische Variante, ohne Power Point-Bezug
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-spreadsheet.svg) | file-spreadsheet | **Tabellenkalkulation'-Datei** generische Variante, ohne Excel-int-Bezug
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-office.svg) | file-office | **Office-Dokument** Word/PowerPoint/Excel |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-excel.svg) | file-excel | **Excel-Dokument** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-video.svg) | file-video | **Video-Datei** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-video2.svg) | file-video2 | **Video-Datei** Alternative |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-word.svg) | file-word | **Word-Dokument** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-ppt.svg) | file-ppt | **PPT-Dokument** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-text.svg) | file-text | **Textdatei** (zB. Word) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/file-generic.svg) | file-generic | **generischer Dateityp** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/filter.svg) | filter | **Suchfilter, Ansichtsfilter** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/filter2.svg) | filter | **Suchfilter, Ansichtsfilter** Alternative|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/fishbowl.svg) | fishbowl | **Goldfisch im Glas** ungenutzt|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-broken.svg) | folder-broken | **nicht erreichbarer Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-date-full.svg) | folder-date-full | **gefüllter Terminordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-date-empty.svg) | folder-date-empty | **leerer Terminordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-edit-empty.svg) | folder-edit-empty | **leerer editierbarer Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-edit-full.svg) | folder-edit-full | **voller editierbarer Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-empty.svg) | folder-empty | **leerer Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-full.svg) | folder-full | **gefüllter Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-group-empty.svg) | folder-group-empty | **leerer Gruppenordner**|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-group-full.svg) | folder-group-full | **gefüllter Gruppenordner**|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-home-empty.svg) | folder-home-empty | **leerer Home-Ordner**|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-home-full.svg) | folder-home-full | **gefüllter Home-Ordne**|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-inbox-full.svg) | folder-inbox-full | **gefüllter Inbox-Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-inbox-empty.svg) | folder-inbox-empty | **leerer Inbox- Ordner**|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-lock-full.svg) | folder-lock-full | **gefüllter geschützter Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-lock-empty.svg) | folder-lock-empty | **leerer geschützter Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-parent.svg) | folder-parent | **übergeordneter Ordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-plugin-market-empty.svg) | folder-plugin-market-empty.svg) | **Ordner Plugin-Marktplatz leer** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-plugin-market-full.svg) | folder-plugin-market-full.svg) | **Ordner Plugin-Marktplatz gefüllt** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-public-empty.svg) | folder-public-empty.svg) | **öffentlicher Ordner, leer** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-public-full.svg) | folder-public-full.svg) | **öffentlicher Ordner, gefüllt** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-topic-empty.svg) | folder-topic-empty | **leerer Themenordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/folder-topic-full.svg) | folder-topic-full | **gefüllter Themenordner** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/forum.svg) | forum | **Forum** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/fullscreen-on.svg) | fullscreen-on | **Vollbild ein** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/fullscreen-off.svg) | fullscreen-off | **Vollbild aus** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/globe.svg) | globe | **Globus/Weltkarte** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/glossary.svg) | glossary | **Glossar** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/graph.svg) | graph | **Graph/Auswertung** generelles Icon für grafische Auswertungen (zB. Evalautionsauswertung) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/group.svg) | group | **Permalink** neu, ehemals Gruppieren |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/group2.svg) | group2 | **Gruppe/gruppieren** Gruppen (Menschen) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/group3.svg) | group3 | **Gruppe/Hierarchie** Gruppen/Hierarchie |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/group4.svg) | group4 | **Gruppe/gruppieren** Gruppieren nach Farbe |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/guestbook.svg) | guestbook | **Gästebuch** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/hamburger.svg) | hamburger | **Hamburger-Menu** für mobile Ansicht|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/home.svg) | home | **Startseite** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/info.svg) | info | **Information** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/info-circle.svg) | info-circle | **Information** für Popover |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/infopage.svg) | infopage | **Freie Infoseite** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/inbox.svg) | inbox | **Nachrichten Eingang** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/outbox.svg) | outbox | **Nachrichten Ausgang** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/install.svg) | install | **Plugin Installation** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/institute.svg) | institute | **Einrichtung** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/item.svg) | item | **Allgemeines Objekt für Kommentare** Kommentarobjekt |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/key.svg) | key | **Password** Password(-verwaltung) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/knife.svg) | knife | **Taschenmesser/Tool** alternative für Tool-Icon |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/learnmodule.svg) | learnmodule | **Lernmodul** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/lightbulb.svg) | lightbulb | **Glühbirne** etwa für Tipps, Ideen oder Brainstorming|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/link-extern.svg) | link-extern | **externer Link** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/link-intern.svg) | link-intern | **interner Link** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/link2.svg) | link2 | **externer Link** Alternative, rechts-orientierte Seiten|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/link3.svg) | link3 | **externer Link** Alternative, links-orientierte Seiten|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/literature-request.svg) | literature-request | **Literaturanfrage** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/literature.svg) | literature | **Literatur** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/lock-locked.svg) | lock-locked | **Schloss im geschlossenen Zustand** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/lock-unlocked.svg) | lock-unlocked | **Sperren/Schloss im geöffneten Zustand** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/log.svg) | log | **Protokoll/Log** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/mail.svg) | mail | **Nachricht** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/maximize.svg) | maximize | **Maximieren** für Widgetsystem|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/medal.svg) | medal | **Prüfungen/erreichte Leistungen** allgemeines Icon für Prüfungen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/mensa.svg) | mensa | **Mensa** Mensa, vegetarisch |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/mensa2.svg) | mensa2 | **Mensa** Mensa |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/metro.svg) | metro | **U-Bahn, Bahn** zB. für Campus-App |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/microphone.svg) | microphone | **Mikrofon** zB. für Medien |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/module.svg) | module | **Modul** in Abgrezung zu Lernmodul oder Plugin |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/money.svg) | money | **Mensa** Bezahlvorgänge, Kostenpflichtiges |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/network.svg) | network | **Netzwerk** ungenutzt |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/news.svg) | news | **Ankündigungen** Ankündigungen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/notification.svg) | notification | **Notifikation** Notifikation |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/notification2.svg) | notification2 | **Notifikation** Notifikation, Alternative |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/outer-space.svg) | outer-space | **Planet/Weltall** ungenutztes Icon, frei zur Nutzung |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/oer-campus.svg) | oer-campus | **OER-Campus** Basisicon für den OER-Campus|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/opencast.svg) | opencast | **Opencast** Icon für das Opencast-Plugin|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/perle.svg) | perle | **Perle** Icon für Perle Plugin |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/permalink.svg) | permalink | **Permalink** Icon zum Abrufen/Verlinken eines Permalinks |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/person.svg) | person | **Person/Profil** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/persons.svg) | persons | **Personen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/person-online.svg) | person-online | **Person online** Person ist online |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/picture.svg) | picture | **Bild** allgemeines Icon für Bilder, zB. in Courseware|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/phone.svg) | phone | **Telefon** klassisches Telefon, abgegrenzt von Handy|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/place.svg) | place | **Ort** Ort/Geoinformation/Place |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/plugin.svg) | plugin | **Plugin** Allgemeines Icon für Plugins |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/powerfolder.svg) | powerfolder | **Clound-Dienst Powerfolder** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/print.svg) | print | **Drucken** Druckfunktionen, Druckansicht |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/privacy.svg) | privacy | **Privatsphäreneinstellungen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/remove.svg) | remove | **Entfernen** Entfernen, auch im Sinne von Verringen (korrespondiert mit dem Entfernen-Zusatz) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/add.svg) | add | **Hinzufügen** Hinzufügen, auch im Sinne von Erhöhen (korrespondiert mit dem Hinzufügen-Zusatz) |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/question.svg) | question | **Frage** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/question-circle.svg) | question-circle | **Frage** für Popover |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/ranking.svg) | ranking | **Ranking** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/radar.svg) | radar. | **Radar** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/refresh.svg) | refresh | **aktualisieren** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/resources.svg) | resources | **Ressource/Ressourcenverwaltung** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/resources-broken.svg) | resources-broken | **kaputte Ressource** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/resource-label.svg) | resource-label | **Ressourcen-Label** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/rescue.svg) | rescue | **Support/Hilfe Alternative** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/roles.svg) | roles | **Rollen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/roles2.svg) | roles2 | **Alternative für Rollen/Rechte** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/rotate-left.svg) | rotate-left | **Bildbearbeitung drehen links** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/rotate-right.svg) | rotate-right | **Bildbearbeitung drehen rechts** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/room.svg) | room | **Basisicon für Räume** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/room2.svg) | room2 | **Basisicon für Räume** Alternative |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/room-clear.svg) | room-clear | **Raum frei** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/room-occupied.svg) | room-occupied | **Raum belegt** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/room-request.svg) | room-request | **Raum anfrage** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/remove.svg) | remove | **Entfernen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/remove-circle.svg) | remove-circle | **Entfernen** für Popover |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/rotate-left.svg) | rotate-left | **Drehen gegen den Uhrzeigersinn** für Bildbearbeitung |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/rotate-right.svg) | rotate-right | **Drehen im Uhrzeigersinn** für Bildbearbeitung |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/rss.svg) | rss | **RSS-Feed** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/schedule.svg) | schedule | **Kalender/Ablaufplan** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/settings.svg) | settings | **Einstellungen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/settings2.svg) | settings2 | **Einstellungen** Alternative für Einstellungen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/share.svg) | share | **Teilen/Exportieren** allgemeines Icon für das Teilen von Objekten |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/search.svg) | search | **Suche** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/seminar.svg) | seminar | **Veranstaltung** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/seminar-archive.svg) | seminar-archive | **Veranstaltungsarchiv** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/smiley.svg) | smiley | **Smiley/Emoji** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/skype.svg) | skype | **Skype** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/span-empty.svg) | span-empty | **Füllstand/Progress: 0%** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/span-1quarter.svg) | span-1quarter | **Füllstand/Progress: 25%** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/span-2quarter.svg) | span-2quarter | **Füllstand/Progress: 50%** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/span-3quarter.svg) | span-3quarter | **Füllstand/Progress: 75%** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/span-1third.svg) | span-1third | **Füllstand/Progress: 33%** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/span-2third.svg) | span-2third | **Füllstand/Progress: 66%** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/span-full.svg) | span-full | **Füllstand/Progress: 100%** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/spiral.svg) | spiral | **Spirale** ungenutzt |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/sport.svg) | sport | **Sport** zB. für Campus-App |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/smiley.svg) | smiley | **Smiley** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/staple.svg) | staple | **Anhang** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/star.svg) | star | **Bewertungsstern** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/stat.svg) | stat | **Statistiken** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/studygroup.svg) | studygroup | **Studiengruppe** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/support.svg) | support | **Support** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/table-of-contents.svg) | table-of-contents | **Inhaltsverzeichnis** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/tag.svg) | tag | **Tag** Tags an Systemobjekten |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/tan.svg) | tan | **TAN** Vergabe, Nutzung von TANs, Prüfungen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/tan2.svg) | tan2 | **TAN** Vergabe, Nutzung von TANs, Prüfungen, Alternative |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/test.svg) | test | **Test** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/timetable.svg) | timetable | **Timetable** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/tools.svg) | tools | **Tools** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/topic.svg) | topic | **Thema** für Themen in Veranstaltungen |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/trash.svg) | trash | **Mülleimer/löschen** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/twitter.svg) | twitter | **Twitter** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/twitter2.svg) | twitter2 | **Alternative Twitter** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/twitter3.svg) | twitter3 | **Alternative Twitter** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/ufo.svg) | ufo | **Ufo** gibt es sie wirklich?|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/unit-test.svg) | unit-test | **Unit-Tests** Unit-Tests |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/upload.svg) | upload | **Upload** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/vcard.svg) | vcard | **vCard/Visitenkarte** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/video.svg) | video | **Video** Video |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/video2.svg) | video2 | **Video** Video/Film |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/view-list.svg) | view-list | **Umschalter Liste/Kacheln Liste** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/view-wall.svg) | view-wall | **Umschalter Liste/Kacheln Kacheln/Wall** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/visibility-checked.svg) | visibility-checked | **Sichtbarkeit an** Umschalter Sichtbarkeit: an |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/visibility-visible.svg) | visibility-visible | **sichtbar/Sichtbarkeit an** Objekt ist sichtbar oder Umschalter Sichtbarkeit: aus |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/visibility-invisible.svg) | visibility-invisible | **unsichtbar** Objekt ist unsichtbar |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/vote.svg) | vote | **Umfrage** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/vote-stopped.svg) | vote-stopped | **angehaltene Umfrage** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/wiki.svg) | wiki | **Wiki** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/wizard.svg) | wizard | **Assistent** Icon für Assistenten |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/youtube.svg) | youtube | **Youtube** |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/zoom-in.svg) | zoom-in | **Zoom in** Zoomen für Bildupload |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/zoom-in2.svg) | zoom-in2 | **Zoom in** Zoomen für Bildupload, Alternative |
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/zoom-out.svg) | zoom-out | **Zoom out** Zoomen für Bildupload|
+| ![](https://develop.studip.de/studip/assets/images/icons/blue/zoom-out2.svg) | zoom-out2 | **Zoom out**Zoomen für Bildupload, Alternative |
+
+
+#### Play Pause und Stop
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/play.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/stop.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/pause.svg)
+
+
+#### Listen und Pfeile
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_1down.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_1left.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_1right.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_1up.svg) Pfeile, Blätterfunktion (zB. Seite weiter, vor/zurück)\\
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_2down.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_2left.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_2right.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_2up.svg) Pfeile zum Verschieben (zB. Objekt eintragen, Vertauschen von Objekten use.)\\
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_eol-down.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_eol-left.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_eol-right.svg)
+
+![](https://develop.studip.de/studip/assets/images/icons/blue/arr_eol-up.svg) Pfeile zum Springen an das Ende einer Liste (Springen an das Ende einer Tabelle, finer Liste von Objekten usw.)
diff --git a/docs/docs/visual-style-guide/inhaltselemente.md b/docs/docs/visual-style-guide/inhaltselemente.md
new file mode 100644
index 0000000..597e419
--- /dev/null
+++ b/docs/docs/visual-style-guide/inhaltselemente.md
@@ -0,0 +1,121 @@
+---
+title: Inhaltselemente
+sidebar_label: Inhaltselemente
+---
+
+Generell gilt: Alle Elemente im Inhaltsbereich müssen durch passende Objekte eingefasst werden.
+Meistens sind dies Content-Boxen (bzw. Fieldareas in Formularen) oder Tabellen.
+Texte und Eingabemöglichkeiten dürfen nicht frei auf dem (weißen) Hintergrund gesetzt werden.
+
+## Text
+Fließtext sollte in Stud.IP durch die Verwendung semantisch entsprechender HTML Attribute strukturiert werden. Dies gilt auch für die Formatierung des Textbildes. Die inhaltliche und logische Struktur des Textes wird somit auf den Quellcode übertragen. Dadurch wird der Text nicht nur lesbarer für den Entwickler, sondern auch zugänglicher für Screenreader.
+
+### Übersicht der HTML Markups
+
+### Überschriften
+Überschriften werden seit Stud.IP 4.0 im Inhaltsbereich nicht mehr verwendet. Entsprechende Auszeichnungen von Überschriften dürfen nur noch im Content der jeweiligen Funktion (etwa Wiki-Texte, Informationsseite oder Foren-Beiträge) verwendet werden, dienen aber nicht mehr der Gliederung oder Beschreibung des Inhaltsbereiches.
+
+### Einfache Listen und Aufzählungen
+Um einfache Listen in Stud.IP darzustellen wird das `<ul>` - Markup verwendet.
+Entsprechende Listenelemente werden mittels `<li>` eingefügt. Auch für Listen gilt, dass diese durch gliedernde Elemente (in der Regel Content-Boxen) eingefasst werden.
+
+Beispiel:
+
+```html
+<ul>
+<li> Eintrag 1</li>
+<li> Eintrag 2</li>
+...
+<li> Eintrag N</li>
+</ul>
+```
+
+## Audio / Video
+TODO
+
+## Lightbox
+Einfache Bildergallerien können in Stud.IP erzeugt werden, indem eine Vorschau des Bilds eingebunden und verlinkt wird. Dieser Link erhält das Attribut `data-lightbox`, wodurch das verlinkte Bild anschliessend in einer Dialog-ähnlichen "Lightbox" angezeigt wird. Sollen mehrere Bilder zusammengefasst werden, so ist das Attribut `data-lightbox` aller verlinkten Bilder mit dem eindeutigen Namen der entsprechenden Lightbox zu füllen, bspw. `data-lightbox="blubber"`.
+
+## Tabellen
+
+Stud.IP verfügt über ein einheitliches und einfach gehaltenes Tabellenlayout, das für alle tabellarischen Darstellungen verwendet werden soll. Kernelemente sind dabei ein sehr einfach zu verwendendes CSS und eine angenehme und unaufdringliche grafische Gestaltung.
+
+Eine Tabelle ist ungefähr so aufgebaut, wie in diesem Beispiel der Teilnehmerseite:
+
+![image](../assets/30d74c57a521f139c0050de6d55866a7/image.png)
+
+### Aufbau & Elemente
+Jede Tabelle setzt sich zusammen aus einer Beschriftung (Label) für die gesamte Tabelle, die Kopfzeile mit den Spaltenbeschriftungen, optionale Trenner-Zeilen, um Segmente in Tabellen gegeneinander abzugrenzen, eine Fußzeile und die normalen Tabellenzeilen. Tabellen selbst sind transparent, die Hintergrundfarbe (in Stud.IP einfaches Weiß) scheint durch.
+
+Grundsätzlich bauen sich die Spalten wie folgt auf:
+
+* Bereich für Bulk-Aktionen: Wenn Bull-Aktionen vorgesehen sind, nehmen diese die erste Spalte auf. Die erste Spalte besteht in diesem Fall aus Chdeckboxen, in der Kopfzeile ist eine Checkbox für das Aktivieren aller Chechboxen vorzusehen.
+* Icon: Das passende Icon für das Objekt
+* Name/Bezeichnung: Der Name des Objektes, dass die Tabellenzeile repräsentiert. Üblicherweise ist der Name klickbar, wenn dadurch der Zugriff auf das Objekt ermöglicht wird (etwa Download im Dateibereich, Link in die Veranstaltung auf der Seite "Meine Veranstaltungen"
+* weitere Spalten mit Namen des Autors/Erstellers, weitere Metadaten eines Objektes
+* Aktionsspalte: Diese nimmt entweder bis zu drei Aktionselemente (in Form von Icons) auf, oder, wenn mehr als drei Aktionen möglich sind, das Aktionsmenu. Dieses hier ist hier definiert:
+
+Der Klick auf den Header einer Spalte sortiert diese, sofern sinnvoll möglich. Ein weiterer Klick kehrt diese Sortierung um.
+
+Aktionselemente finden sich außerdem an folgenden Stellen:
+
+* Elemente, die sich auf die gesamte Tabelle beziehen: Oberhalb der Tabelle (Label-Zeile) in Form von Icons/Aktionsmenu.
+* Elemente die sich auf eine Zeile beziehen: Pro Spalte auf der rechten Seite in Form von Icons/Aktionsmenu.
+* Elemente, die sich auf ausgewählte Zeilen beziehen: Im Footer der Seite mit dazu gehörigen Checkboxen auf der linken Seite in jeder Tabellenzeile (Die Checkbox in der Tabellenkopfzeile neben den Überschriften der Spalten markiert alle sichtbaren Zeilen der Tabelle).
+
+### CSS
+
+Das folgende Beispiel verdeutlicht eine simple Tabelle, die nach aktuellen Vorgaben aufgebaut ist:
+
+```html
+<table class="default">
+ <caption>
+ <span class="actions">
+ <!-- Bereich für Aktions-Icons, die die gesamte Tabelle umfassen -->
+ </span>
+ TutorInnen
+ </caption>
+ <colgroup>
+ <col style="width: 20%">
+ <col>
+ <col style="width: 80%">
+ </colgroup>
+ <thead>
+ <tr class="sortable">
+ <th>Nr.</th>
+ <th>Nachname, Vorname</th>
+ <th style="text-align: right">Aktionen</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td style="text-align: right">01</td>
+ <td>Kater, Cornelis</td>
+ <td>
+ <!-- Bereich für Aktions-Icons, die die Zeile Tabelle umfassen -->
+ </td>
+ </tr>
+ </tbody>
+ <tfoot>
+ \\
+ <tr>
+ <td colspan="3">
+ <!-- Bereich für Aktions-Icons, die die gesamte Tabelle umfassen -->\\
+ </td>
+ </tr>
+ </tfoot>
+</table>
+```
+
+
+Das Beispiel zeigt, das relativ wenige CSS-Stile verwendet werden müssen, um das Standard-Design von Stud.IP zu erhalten. Die Tabelle wird als Default-Klasse definiert, dadurch ergibt sich bereits der größte Teil des Aussehens.
+Im Beispiel nicht gezeigt wird ist eine eigene Klassen namens `collapsable`, die in einem `<tboby>`-Element zugewiesen
+wird, wenn Tabellen in sich gegliederte (und auch zuklappbare) Bereiche aufweisen.
+
+Weitere Hinweise:
+
+* Jede Tabelle muss ein Label führend, das die Tabelle klar benennt
+* Weitere Gestaltungselemente sollen nicht eingeführt werden (ggf. bitte Rücksprache mit der GUI-Gruppe halten)
+* Tabellenbereiche können auf- und zuklappbar gestaltet werden
+* Hierarchische Tabellenstrukturen sind zukünftig nicht mehr vorgesehen. Stattdessen soll das das Auswählen eines Knotens (der in der Regel einer Zeile entspricht) die nächste Ebene springen, die dann vollständig angezeigt wird (übergeordnete Ebene werden ausgeblendet). Beispiel dafür ist die Umsetzung des Dateibereiches ab der Version Stud.IP 4.0
+* Dieses neue Tabellenlayout gilt lediglich für rein tabellarische Darstellungen. Systembereiche, die bisher Tabellen genutzt haben, um den allgemeinen Seitenaufbau zu beeinflussen, dürfen nicht au die diese Stile umgestellt werden. Hier empfiehlt sich, entweder das bestehe Aussehen (zunächst) beizubehalten oder ohne HTML-Tabellenstrukturen neue aufzubauen. In der Regel sind Forms hier die bessere Alternative.
diff --git a/docs/docs/visual-style-guide/logos.md b/docs/docs/visual-style-guide/logos.md
new file mode 100644
index 0000000..9ede224
--- /dev/null
+++ b/docs/docs/visual-style-guide/logos.md
@@ -0,0 +1,30 @@
+---
+title: Logos
+sidebar_label: Logos
+---
+
+
+
+Für Stud.IP existiert seit 1999 ein feststehendes Logo in zwei Varianten (mit Bildmarke und nur Schrift), das im Jahr 2014 mit Erscheinen der Version 3.0 modernisiert wurde.
+
+Varianten bis 2014
+
+![image](../assets/1ce06d185917489aa6f3da79f9a491d6/image.png)
+
+![image](../assets/8fcb015158becfc136f6d2db84ef27bf/image.png)
+
+Varianten ab 2014/Version 3.0
+
+![image](../assets/c061aa06f3acdf6003dc54bd1e677ebe/image.png)
+
+![image](../assets/829b531d3dbd3e2bde37443f1394f821/image.png)
+
+Das Download-Paket mit allen Logo-Varianten kann dauerhaft unter folgender Adresse herunter geladen werden:
+
+http://bit.ly/studip-logo
+
+In diesem Paket enthalten sind die beiden Grundformen in verschiedenen Farbvarianten enthalten sowie Erweiterungen (Logo für Stud.IP e.V., Stud.IP mobile usw.). Das Paket wird regelmäßig aktualisiert, wenn weitere Varianten vom Grafiker erstellt werden.
+
+## Verwendung
+
+Generell soll zukünftig die Variante mit Bildmarke in der farbigen Variante verwendet werden. Für dunkle Hintergründe gibt es eine Variante, die überwiegend in weiß gehalten ist. Nur in ausgewählten Fällen (zB. im Fließtext oder als Wasserzeichen) kann die Variante ohne Bildmarke und ggf. in der einfarbigen Version verwendet werden.
diff --git a/docs/docs/visual-style-guide/navigation.md b/docs/docs/visual-style-guide/navigation.md
new file mode 100644
index 0000000..38dd237
--- /dev/null
+++ b/docs/docs/visual-style-guide/navigation.md
@@ -0,0 +1,106 @@
+---
+title: Navigation
+sidebar_label: Navigation
+---
+
+Die Navigation in Stud.IP ist in mehreren Ebenen organisiert. Es ist zu unterscheiden in:
+
+* Hauptnavigation: Die Kopfzeile des Systems. Von hier aus werden komplette Funktionsbereiche erschlossen. Jeder
+ dieser Bereiche entspricht einem der Hauptpunkte in der Sitemap und jeder dieser Bereiche präsentiert sich mit einem eigenen Reitersystem.
+* Scopes: Jeder Hauptbereich (zB. Profil oder Community) bringt einen Scope mit, der die Funktionen eines Bereiches
+ aufnimmt. Ein Scope entspricht jeweils der zweiten Ebene in der Sitemap. Eine Funktion darf nur an einer einzigen
+ Stelle in einem Scope eingehangen werden. Somit hat jede Funktion eine eindeutige Zuordnung zu einem der Hauptbereiche.
+* Navigation in der Sidebar: Verschiedene Aufgaben innerhalb einer Funktion finden sich im Navigationsbereich der
+ Sidebar. Diese führen an dieser Stelle zu einem neuen Seitenaufruf (im Gegensatz zu Aktionen in der Sidebar).
+* Aus der Navigation der Sidebar sind auch Links in andere Hauptbereiche möglich, sollten jedoch vermieden werden.
+ Im Idealfall bleibt auch die Navigation einer Funktion innerhalb ihrer eigenen Aufgaben bzw. innerhalb des
+ jeweiligen Scopes. Ein Eintrag in der Navigation der Sidebar entspricht der dritten Ebene in der Navigation.
+
+Weitere Hinweise zum Aufbau der Sidebar und ihrer unterschiedlichen Widgets findet sich im entsprechenden [Bereich
+des Styleguides](#Sidebar)
+
+## Kopfzeile
+
+Die Kopfzeile leitet jede Seite ein und bietet Zugriff auf alle Kernbestandteile von Stud.IP:
+
+Mini:kopfzeile.png
+
+Je nach Rechtestufe des angemeldeten Nutzers und eingerichteten Systemplugins werden unterschiedliche Systembereiche von hier aus zugänglich gemacht.
+
+Die Kopfzeile sieht den größten Gestaltungsspielraum für Anpassungen an die Corporate Identity des Betreibers vor.
+
+Folgende Anpassungen sind hier möglich:
+* Einfügen des eigenen Logos an beliebiger Position (Vorschlag: Rechts neben dem Stud.IP Logo)
+* Einfügen weiterer eigener Links in der Kopfzeile (Vorschlag: links neben der globalen Suchen)
+
+Noch einige Hinweise zur Eigenanpassung der Kopfzeile:
+* Entfernen Sie nicht die Icons aus der Kopfzeile, da die Icons ihre Gestaltung innerhalb des Systems wiederholt
+ auftauchen und damit eine Verbindung zu dieser Navigation schaffen
+* Entfernen Sie nicht die Beschriftung der Icons, da die Nutzer über diese Beschriftung wichtige Erklärungen erhalten
+ und der Text auch in anderen Systemsprachen zur Verfügung steht.
+* Ändern Sie nicht die Reihenfolge der Icons oder teilen Sie die Icons in mehrere Zeilen auf.
+* Ordnen Sie Kopfzeile nicht an andere Stellen (etwa als Seitenleiste) an. Das Stud.IP-System benötigt an einigen
+ Stellen teilweise eine sehr breite Darstellung. Die Kopfzeile ist in dieser Form am besten auf das System abgestimmt.
+
+## Reite (Scopes)
+Scopes fassen die Funktion eines Hauptbereiches (etwa alle Funktionen innerhalb einer Veranstaltung oder innerhalb des Nachrichtensystems) zusammen.
+
+Mini:style_reiter.jpg
+
+Stud.IP ergänzt in einem Scope (ebenso wie in der Hauptnavigation) automatisch einen "Überlauf", der in einem
+Drop-Down-Menü alle Icons aufnimmt, die nicht mehr in die horizontale Darstellung (je nach Bildschirmbreite) passen
+würde. Grundsätzlich sollte beim Entwerfen neuer Funktionen darauf achten, möglichst knappe Bezeichnungen zu wählen,
+sodass möglichst viele Funktionen nebeneinander Platz finden. Die Breite der jeweiligen Beschriftungen bedingt die Breite des Scopes!
+
+## Sidebar
+
+### Vorbemerkung
+
+Das Konzept der Infoboxen (Stud.IP-Versionen bis 3.0) hat sich grundlegend geändert zum Sidebar-Konzept (ab Stud.IP
+3.1), das viele der Funktionen aus den alten Infoboxen aufnimmt, jedoch nicht direkt ersetzt. In Rahmen dieser
+Umstellung wurde die 3. Navigationsebene als Zeile unterhalb der Reiter in ein Navigationswidget der Sidebar verlegt.
+
+Attach:Style/Sidebar-dafault.jpg
+
+### Kurzbeschreibung
+Die Sidebar befindet sich an fester Position am linken Rand einer Stud.IP-Seite. Die Sidebar ersetzt die Infobox älterer Stud.IP-Versionen und enthält mindestens eins, meistens mehrere Widgets. In der Sidebar befinden sich innerhalb von diesen Widgets die Elemente der 3. Navigationsebene, Aktionen, Ansichtsoptionen, seiteninterne Suchmöglichkeiten und Exportfunktionen. Sofern diese Standardwidgets nicht passend sind, kann eine Seite weitere Widgets haben.
+Die Sidebar besitzt zudem Orientierungsbild im Kopfbereich, das den Namen der Seite enthält, das Baisisicon des jeweiligen Bereiches zeigt und einen Avatar aufnehmen kann.
+Jede Seite sollte eine Sidebar besitzen.
+
+### Aufbau & Elemente
+
+#### Orientierungsbild
+Das Orientierungsbild ist 520px breit und 200px hoch. Zu allen Basisfunktionen (bzw. aufbauend auf deren Icons) werden entsprechende Orientierungsbilder ausgeliefert. Grundsätzlich können Standorte diese Bilder tauschen, sollten aber darauf achten, dass Bildinhalt und Helligkeit zum umgebenden Design passen. Im Zweifel steht die Stud.IP-GUI-Gruppe bereit, weitere Bilder zu erstellen oder Tipps zu geben, wie man eigenen Bilder integrieren kann.
+
+#### Typen von Widgets
+| Typ | Beschreibung |
+| ---- | ---- |
+| Navigation | Enthält automatisch die 3. Navigationsbene entsprechend der Stud.IP-Navigationsstruktur (ehemals 3. Navigationsebene unterhalb der Reiterleiste). Navigationspunkte springen auf andere Seiten aber bleiben idealerweise innerhalb eines Navigationskontextes (=Reitersystem). Die aktuell gewählte Seite wird mit einem blauen Pfeil markiert. Navigationspunkte zeigen keine Icons. |
+| Aktionen | Enthält Aktionen, die den Inhalt der aktuellen Seite beeinflussen. Aktionen öffnen grundsätzlich einen Dialog und verlassen somit nicht den aktuellen View, den der Nutzer sieht. |
+| Ansichten | Diese enthalten Ansichtsoptionen bzw. Filter, die den angezeigten Content auf der jeweiligen Seite einschränken. Die jeweils gewählte Ansicht bzw. der Filter ist mit einem gelben Pfeil markiert. |
+| Suche | Ein Such-Widget ist seitenspezifisch, ermöglicht also das Suchen innerhalb des Contents der Seite. Idealerweise gilt, dass eine Suche hier nur innerhalb des Contents filtert, den ich auf dieser Seite insgesamt sehen kann bzw. erreichen kann. Wenn der Content einer Seite selbst ein Suchergebnis liefert (z.B. bei allen Suchfunktionen in Stud.IP) muss diese Suche außerhalb der Sidebar, z.B. in einer Content-Box im Content-Bereich der Seite realisiert werden. Ein Suchwidget könnte dann theoretisch den gefunden Content dynamisch Einschränken, idealerweise ohne Reload der Seite |
+| Export | Hier werden alle Funktionen aufgenommen, die konkret eine Datei (z.B. PDF, XLS-Export, CSV-Datei) zum Download anbieten. |
+
+Grundsätzlich beginnen Seiten mit der Navigation und den Aktionen, dann folgende weitere Widgets (in der Regel Suche, Ansichten oder Export). Die weitere Widgets können je nach Nutzungshäufigkeit der jeweiligen Seite platziert werden, die ersten beiden Positionen sind in der Reihenfolge fest vorgegeben.
+
+
+#### Weitere Typen von Widgets
+
+Gelegentlich tauchen folgende Type auf:
+
+| Type | Beschreibung |
+| ---- | ---- |
+| Einstellungen | Für Einstellungen, die sich direkt auf die Seite auswirken und schnell in der Sidebar vorgenommen werden sollen |
+| Merkliste | Für das Zwischenspeichern von beliebigen Objekten |
+
+
+### Was nicht in die Sidebar gehört
+
+* Hilfetexte: Bisher oft in der Infobox verwendet, gehören erklärende oder einleitende Texte über die Funktion einer Seite nicht mehr in die Sidebar. Der beste Platz dafür ist die in der Version 3.1 neu geschaffene Hilfe-Lasche, in der auch Touren gestartet werden und der Link zum Hilfe-Wiki zu finden ist.
+* Formulare: Mit Ausnahme eines Eingabefeldes für das Such-Widget gehören Formulare nicht in die Sidebar.
+
+### Sonst noch zu beachten
+
+* Für die Sidebar gibt es eine feststehende API, die für die Erstellung verwendet werden muss.
+* Die Umstellung des Admin-Bereiches erfolgt voraussichtlich im Rahmen der Arbeiten der Version 3.2, bis dahin ist nur die Navigation in das entsprechende Widget verlegt.
+* Außer im Navigationswidget sollten in der Sidebar eindeutige und passende Icons in der Farbe blau verwendet werden und klickbar sein. Insbesondere Aktionen profitieren von der leichten Auffindbarkeit durch Icon + Text.
diff --git a/docs/docs/visual-style-guide/seitenaufbau.md b/docs/docs/visual-style-guide/seitenaufbau.md
new file mode 100644
index 0000000..1a00b62
--- /dev/null
+++ b/docs/docs/visual-style-guide/seitenaufbau.md
@@ -0,0 +1,56 @@
+---
+title: Seitenaufbau
+sidebar_label: Seitenaufbau
+---
+
+Jede Seite von Stud.IP ist auf gleiche Art und Weise aufgebaut und enthält folgende Elemente:
+
+## Kopfzeile
+
+Einleitende Zeile, die eine systemweite Suche beinhaltet. Wird die Hauptnavigation durch Scrollen aus dem Sichtbereich verschoben, wird diese in kompakter Form in der Kopfzeile aufgenommen. Die Kopfzeile kann vom Betreiber erweitert werden.
+
+## Hauptnavigation
+
+Sie leitet jede Seite ein und ist das feststehende Navigationselement, das die Systembereiche miteinander verbindet. Die Zusammenstellung der Kopfzeile hängt von den globalen Rechten des Benutzers ab. Die Kopfzeile repräsentiert die 1. Navigationsebene.
+Scopes: Scopes verbinden bestimmte Funktionen eines Hauptbereiches, etwa dem Nachrichtensystem oder alle Funktionen innerhalb von Veranstaltungen. Ein Scope besitzt einen Bereich bzw. ein Icon in der Hauptnavigation und verweist auf mehrere Funktionen. Ein Scope repräsentiert bzw. beinhaltet stets die 2. Navigationsebene.
+
+## Sidebar
+Diese befindet sich am linken Bildschirmrand und enthält in definierter Form mehrere Widgets, etwa
+Navigation (der gewählten Funktion im gewählten Scope), Aktionen, Ansichten, Export und ggf. weitere Widgets.
+
+### Navigationswidget
+Dieses Widget erscheint stets als erstes Widget und repräsentiert, wenn vorhanden, die 3. Navigationsebene.
+
+### Inhaltsbereich
+Hier werden sämtliche Inhalte dargestellt. Ein Inhaltsbereich wird aus Tabellen und ContentBoxen
+bzw. Eingabefeldern gebildet. Für den Inhaltsbereich existieren feste Elemente, aus denen dieser gestaltet werden muss.
+Der Inhaltsbereich umfasst alle jene Inhalte, die von der jeweiligen Funktion angezeigt oder bearbeitet werden.
+In diesem Bereich finden alle Objektmanipulationen und die Inhaltsanzeige statt. Entscheidend ist, dass in diesem Bereich eigentlich nur Objekte (die als solche gekennzeichnet sind, siehe später), sie manipulierende Methoden und verschiedene weitere (Meta-)Informationen zu diesen Objekten platziert werden sollten. Erklärungstexte, verweise auf andere Systemteile und andere Navigationselemente dürfen nicht in diesem Bereich erscheinen.
+Für die Gestaltung sollten standardisierte grafische Elemente verwendet werden, Funktionen, die bereits in ähnlicher Weise im System vorhanden sind, müssen sich in der Bedienung daran anlehnen. Gerade im Inhaltsbereich muss es das erklärte Ziel sein, mit bekannten Elementen zu arbeiten, um dem Nutzer eine vertraute Umgebung &#8211; auch bei neuen Funktionen &#8211; zu bieten.
+
+Einige grundsätzliche Hinweise zur Gestaltung des Inhaltsbereiches:
+* Vermeiden Sie, im Inhaltsbereich der Seiten Texte frei zu platzieren. Es gibt eine Reihe von grafischen Gestaltungsmöglichkeiten, die im folgenden beschrieben werden, mit denen Sie jedwede Inhalte innerhalb des Inhaltsbereiches markieren und jeweils von anderen Objekten abgrenzen können.
+
+
+## Fußzeile
+Diese enthält weitere Links und Verweise, die analog zur Kopfzeile vom Betreiber erweitert werden kann.
+
+//TODO: Screenshot einer idealtypischen Seite
+
+Die eigentliche Seite setzt sich aus Sidebar und Inhaltsbereich zusammen. Beide Bereiche werden vom einem
+Seitentitel eingeleitet. Im Gegensatz zum Design bis Stud.IP 3.5 bringt der Inhaltsbereich nun keinen eigenen Titel
+(bisher teilweise als h1-Objekt gestaltet) mit.
+
+## Seitentitel
+
+* Der Titel muss namensgleich mit dem Eintrag in der Navigation in der Sidebar sein
+* Bei Veranstaltungen wird automatisch der Name der Veranstaltung mit ausgegeben (gleiches gilt für den Einrichtungsbereich bei gewählter Einrichtung)
+
+## Weitere Vorgaben
+
+* Jede Seite enthält zwingend eine Sidebar.
+* Jede Sidebar enthält mindestens ein Schmuckbild.
+* am rechten Rand ist der Zugriff auf die Hilfe (Fragezeichen-Icon) als Abschluss des Seitentitels vorgesehen.
+* Aktionen der Sidebar (zu finden im gleichnamigen Widget) werden in Dialogen ausgeführt.
+
+Weitere Informationen zur Sidebar: siehe Abschnitt [Sidebar](seitenaufbau#sidebar)
diff --git a/docs/docs/visual-style-guide/sprache.md b/docs/docs/visual-style-guide/sprache.md
new file mode 100644
index 0000000..15d66ba
--- /dev/null
+++ b/docs/docs/visual-style-guide/sprache.md
@@ -0,0 +1,19 @@
+---
+title: Sprache
+sidebar_label: Sprache
+---
+
+## Allgemein
+Keine unnötigen Texte, schon gar keine, die dem Nutzer erzählen, was er auf dieser Seite tun oder lassen kann
+* Duzen vs. Siezen
+* ...
+
+* TODO: Regeln übernehmen und übersetzen http://developer.android.com/design/style/writing.html
+
+## Terminologie
+* Allgemein: Keine Fachausdrücke: Speak the User's Language
+* "Vokabelliste"
+ * Veranstaltung
+ * Inhaltselemente
+ * Ankündigungen
+ * ...
diff --git a/docs/docs/visual-style-guide/suche.md b/docs/docs/visual-style-guide/suche.md
new file mode 100644
index 0000000..8694513
--- /dev/null
+++ b/docs/docs/visual-style-guide/suche.md
@@ -0,0 +1,85 @@
+---
+title: Suche
+sidebar_label: Suche
+---
+
+* Objekte, nach denen gesucht werden kann (und was man dann damit macht)
+ * Personen
+ * Adressbuch: zum Adressbuch hinzufügen; zu einer Gruppe im Adressbuch hinzufügen
+ * Globale Einstellungen: Globale Eigenschaften ändern
+ * Veranstaltungen: als Dozent/Tutor zu einer Veranstaltung hinzufügen
+ * Veranstaltungen: als Teilnehmer zu einer Veranstaltung hinzufügen
+ * Messaging: zur Empfängerliste hinzufügen
+ * allg. Personensuche: persönliche Homepage aufrufen, Nachricht schicken
+ * Einrichtungen: zur Mitarbeiterliste hinzufügen
+ * Ressourcenverwaltung: als lokalen/globalen Ressourcenadmin eintragen
+ * Veranstaltungen
+ * Veranstaltungssuche für Studis: zu einzelnen Veranstaltungen wechseln (= zur Übersichtsseite wechseln); persönliche Homepage einzelner Veranstaltungen aufrufen
+ * Veranstaltungssuche für Admins: Veranstaltung zum Bearbeiten auswählen, diverse "Batch-Aktionen" (Sichtbarkeit, Sperrebenen usw.) ausführen
+ * im Archiv: diverse Aktionen (Details aufrufen, Dateisammlung herunterladen, endgültig löschen, ...)
+ * Veranstaltungshierarchie: Veranstaltung(en) zu Studienbereichen hinzufügen
+ * Einrichtungen
+ * Zur Einrichtung in Stud.IP wechseln
+ * Zur Website der Einrichtung wechseln
+ * eine E-Mail an den Ansprechpartner schreiben
+ * Ressourcen
+ * diverse Aktionen (die man auch sonst an Ressourcen vornehmen kann: Belegungsplan aufrufen, Eigenschaften bearbeiten, ...)
+ * Bereiche (in News-, Voting- und Evaluationsverwaltung)
+ * Neues Voting/neuen Test in einem Bereich erstellen
+ * Bereich auswählen (um dort anschließend eine News zu erstellen/bearbeiten)
+ * Forenbeiträge
+ * diverse Aktionen (die man auch sonst an Forenbeiträgen vornehmen kann: antworten, zitieren, bearbeiten, ...)
+ * Öffentliche Evaluationsvorlagen
+ * zu den eigenen Evaluationsvorlagen hinzufügen
+ * Wikiseiten
+ * Wikiseiten aufrufen
+ * Literatur (= Einträge in Literaturlisten)
+ * Details aufrufen
+ * in Merkliste eintragen
+ * zum Eintrag im externen Katalog (OPAC) wechseln
+* Formulierung der Suchanfrage
+ * Variante 1: nur ein einzeiliges Textfeld
+ * Variante 2: Textfeld(er) plus weitere Formularfelder (z. B. Veranstaltungssuche, Personensuche, Ressourcensuche)
+
+* Auto-Complete
+ * ...
+
+* Auslösen der Suchanfrage
+ * Variante 1: Klick auf Icon "Lupe" (z. B. Suche nach Dozenten auf admin_seminare1.php, Suchen eines Wunschraums auf admin_room_request.php)
+ * Variante 2: Klick auf Button "Suche starten" (z. B. Suche nach Veranstaltungen auf sem_portal.php, Suche nach Ressourcen, Suchen im Archiv,
+ * Variante 3: Klick auf Button "suchen" (z. B. Suche nach Personen auf browse.php, Suche nach Literatur auf lit_search.php)
+
+## Mögliche Guidelines
+* Die Eingabe von Suchbegriffen erfolgt grundsätzlich in ein einzeiliges Texteingabefeld (input type="text").
+* Sofern das Suchformular nur aus diesem Textfeld besteht, wird die Suche durch Klicken auf ein rechts neben dem Textfeld angebrachtes Lupen-Icon ausgelöst.
+* Besteht das Suchformular aus mehreren Formularfeldern (z. B. zum Einschränken der Suchergebnisse), so wird die Suche grundsätzlich durch einen Button mit dem Text "Suche starten" ausgelöst.
+
+## Darstellung von Suchergebnissen
+
+### Stand der Dinge
+* **Variante 1:** Dropdownliste ersetzt Eingabefeld (Beispiele: Zuweisen eines Dozenten zu einer Veranstaltung auf admin_seminare1.php; Auswählen eines Wunschraums beim Formuliren einer Raumanfrage)
+* **Variante 2:** Aufklappbare (bzw. bereits aufgeklappte) Listenelemente (z. B. Ressourcensuche, Literatursuche)
+* **Variante 3:** Einfache Liste (z. B. Veranstaltungssuche sem_portal.php, Personensuche browse.php und_new_user_md5.php)
+* **Variante 4:** Aufgeklappte Elemente innerhalb einer hierarchischen Liste von ansonsten zugeklappten Elementen (Suche nach Einrichtungen institut_browse.php)
+* **Variante 5:** Mehrzeilige Selectbox (Freie Suche nach Personen in der Gruppenverwaltung in Einrichtungen, Gruppenverwaltung im Adressbuch)
+
+### Mögliche Guidelines
+* Oberhalb der Suchergebnisse soll die Anzahl gefundener Elemente ausgegeben werden.
+* Unterscheidung nach Verwendungszweck der Ergebnisse
+ * **Auswählen genau eines Elements der Ergebnisliste** (z. B. Zuordnung eines Dozenten zu einer Veranstaltung, Auswählen eines Wunschraumes in einer Raumanfrage)
+ * **Auswählen ggf. mehrerer Elemente der Ergebnisliste** (z. B. Zuordnung von Veranstaltungen zu Studienbereichen in der Studienbereichsverwaltung, Zuordnen von per Suche gefundenen Personen in der Gruppenverwaltung in Einrichtungen)
+ * **Anklicken genau eines Elements der Ergebnisliste, um es anzusteuern** (z. B. Veranstaltungssuche [sem_portal], Personensuche [browse.php])
+
+### Fragen/Ideen
+* Wie hängen die Guidelines zu den Suchergebnissen mit denen zu Elementlisten zusammen? M.a.W.: Wann sollen Suchergebnisse in Form von Elementlisten ausgegeben werden, für die dann automatisch die dort definierten Regeln greifen?
+ * Idee: Suchergebnisse werden grundsätzlich in Form von Elementlisten dargestellt. (Ggf. mit definierten Ausnahmen wie z.B. Dozentensuche auf admin_seminare1.php)
+* Wovon kann/sollte man die Art der Darstellung abhängig machen?
+ * Verwendungszweck der Suchergebnisse (s.o.)
+ * Art und Darstellung des Suchformulars, aus dem die Suchergebnisse stammen
+ * Zur Verfügung stehender Platz (z.B. innerhalb von auf- und zuklappbaeren Elementlisten, Beispiel: Personensuche innerhalb der Gruppenverwaltung in Einrichtungen)
+ * Art der gesuchten Elemente (Personen vs. Veranstaltungen vs. Ressourcen vs. ...)
+ * Vorhersagbarkeit der Treffermenge (iframe oder selectbox bei potenziell großen Treffermengen)
+ * %blue% Denkbar ist eine Matrix mit zwei Kriterien, in der für jede Kombination aus Kriterienausprägungen ein eigenes Set von Darstellungsregeln definiert ist
+* Blättern
+ * Soll (grundsätzlich bzw. nach definierten Kriterien) innerhalb der Suchergebnisse geblättert werden können?
+ * Wenn ja, wie soll das Blättern dargestellt werden? (Ggf. generelle Regeln für Blätterfunktion)
diff --git a/docs/docs/vuejs/start.md b/docs/docs/vuejs/start.md
new file mode 100644
index 0000000..ffa938c
--- /dev/null
+++ b/docs/docs/vuejs/start.md
@@ -0,0 +1,63 @@
+---
+title: Stud.IP VUEJS
+slug: /vuejs/
+sidebar_label: Einführung
+---
+
+Seit Stud.IP 4.5 ist [Vue.js](https://vuejs.org/) in Stud.IP verfügbar und wird für einige Komponenten genutzt.
+
+
+** `STUDIP.Vue`**
+
+Mit der Version 5.0 von Stud.IP soll die Nutzung von Vue.js weiter vorangetrieben werden. Dafür werden über das Objekt `STUDIP.Vue` in Javascript Methoden bereitgestellt, die die Integration erleichtern sollen, indem manche Dinge abstrahiert bzw. generalisiert werden.
+
+Vue.js wird nun als Chunk geladen und ist somit nicht mehr sofort auf jeder Seite verfügbar. Alle über `STUDIP.Vue` verfügbaren Methoden kümmern sich um das Laden und sind daher der präfertierte Weg, Vue.js zu nutzen.
+
+** `STUDIP.Vue.load()` **
+
+Über diese Methode wird Vue.js geladen und über ein Promise zurückgegeben.
+
+```Javascript
+STUDIP.Vue.load().then(({Vue, createApp, eventBus, store}) => {
+ // ...
+});
+```
+
+** `Vue` **
+
+Das verwendete `Vue`-Objekt kann dazu genutzt werden, um Komponenten oder Direktiven global zu registrieren.
+
+** `createApp(options)`**
+
+Diese Methode lädt den Vue-Chunk und erstellt eine App. Die Parameter sind wie folgt:
+
+| Parameter | Beschreibung |
+| ---- | ---- |
+|`options`|Optionen wie `data`, `methods` oder `computed`|
+
+Anschliessend kann diese App mittels `app.$mount(element)` auf das gewünschte Element gemountet werden.
+
+Diese Abstraktion ist trivial, aber soll die Erstellung einer App kapseln, falls sich die API von Vue.js ändert und einen leichten Einstieg bieten.
+
+** `eventBus` **
+
+Der `eventBus` kann genutzt werden, um global Events abzusetzen bzw. auf diese zu horchen.
+
+** `store` **
+
+Der `store` ist eine [vuex](https://vuex.vuejs.org/guide/)-Instanz, die für die Datenhaltung zuständig ist.
+
+Weitere Details hierzu werden folgen...
+
+** `STUDIP.Vue.emit(eventName, ...args)` und `STUDIP.Vue.on(eventName, ...args)` **
+
+Über diese beiden Methoden können Nachrichten und Daten zwischen Vue-Komponenten bzw. dem umliegenden System und Vue-Apps ausgetauscht werden. Intern wird ein Event-Bus realisiert, der über ein globales Mixin in jeder Vue-Komponente über die Methoden `globalEmit(eventName, ...args)` bzw. `globalOn(eventName, ...args)` bereitgestellt werden.
+
+** `[data-vue-app]` **
+
+Über das HTML-Attribut `[data-vue-app]` kann ohne weitere Initialisierung eine Vue-App erstellt werden. Über den Inhalt des Attributs können noch weitere Angaben zu den Daten oder verwendeten Komponenten gemacht werden, indem ein entsprechendes Objekt übergeben wird. Mögliche Optionen:
+
+| Parameter | Beschreibung |
+| ---- | ---- |
+|`id`|Die Id der App. Ist diese gesetzt und es gibt ein Objekt `STUDIP.AppData`, welches unter der übergebenen Id Daten bereithält, so werden diese Daten der App übergeben |
+|`components`|Die von der App verwendeten Komponenten als Array.|
diff --git a/docs/docs/vuejs/vuex.md b/docs/docs/vuejs/vuex.md
new file mode 100644
index 0000000..ee9032b
--- /dev/null
+++ b/docs/docs/vuejs/vuex.md
@@ -0,0 +1,230 @@
+---
+title: VUEX
+slug: /vuejs/vuex
+sidebar_label: VUEX
+---
+
+Natürlich wollen wir die Stud.IP-JSONAPI auch gerne in Vue.js verwenden. Mit den bekannten Tools wie XMLHttpRequest und fetch bzw. mit Wrapper-Bibliotheken wie axios ist das einfach möglich. Können wir das aber noch ein bisschen praktisch nutzbarer gestalten?
+
+Wichtig in dem Zusammenhang ist, dass Vue.js ein komponentenbasiertes Framework ist. Das generierte HTML entsteht aus mehrfach verwendbaren Komponenten, die letztlich einen Komponentenbaum bilden.
+
+### State-Management mit vuex
+
+Ein häufig wiederkehrendes Problem in komponentenbasierten Web-Frameworks ist die Frage des State-Managements. Wie gestalte ich den Zugriff auf den „State“, die Daten, meiner Applikation. Die üblichen Ansätze, die in Vue.js verwendet werden, ist die Weitergabe von relevanten Daten von Elternkomponenten an ihre Kindkomponenten.
+
+Ein Beispiel:
+
+* Zwei Komponentenknoten benötigen dieselben Daten.
+* Sie sind aber nur sehr weit entfernt miteinander verwandt, hängen also z.B. jeweils tief in zwei unterschiedlichen Hauptästen
+
+Das führt in der Regel dazu, dass die Daten dann vom nächsten gemeinsamen Verwandten bereitgestellt und dann über die komplette Verwandtschaftslinien durchgereicht werden müssen, obwohl die dazwischen liegenden Knoten nichts mit diesen Daten zu tun haben.
+
+An dieser Stelle hakt `vuex` ein und bietet einen zentralen Store an, auf den **alle** Komponenten zugreifen können.
+
+Wenn wir Daten aus der JSONAPI auslesen, lohnt es sich also, diese in den Store einzuspeisen, damit es unter anderem klar definierte Punkte gibt, an denen auf die JSONAPI zugegriffen wird, um performanzproblematische Doppelungen zu verhindern.
+
+**Wir wollen also unbedingt eine Kombination von JSONAPI und vuex haben.**
+
+### `reststate-vuex`
+
+Zum Glück gibt es schon ein paar Bibliotheken, um JSONAPI und `vuex` zu verknüpfen. Für die Implementation der *Courseware 5* haben wir uns für eine Bibliothek entschieden, die wir um weitere Möglichkeiten erweitert haben und gegenwärtig vom ELAN e.V. gepflegt wird:
+
+https://github.com/elan-ev/reststate-vuex
+
+### Setup
+
+Das Setup, um `reststate-vuex` zu nutzen, konfiguriert eine `axios`-Instanz mit unserer JSONAPI-Schnittstelle. Diesen Code sehen wir ja in der Regel nur sehr selten.
+
+```javascript
+const getHttpClient = () =>
+ axios.create({
+ baseURL: STUDIP.URLHelper.getURL(`jsonapi.php/v1`, {}, true),
+ headers: {
+ 'Content-Type': 'application/vnd.api+json',
+ },
+ });
+
+// […]
+
+const store = new Vuex.Store({
+ modules: {
+ courseware: CoursewareModule,
+ ...mapResourceModules({
+ names: [
+ 'courses',
+ 'courseware-blocks',
+ 'courseware-block-comments',
+ 'courseware-containers',
+ 'courseware-instances',
+ 'courseware-structural-elements',
+ 'courseware-user-data-fields',
+ 'courseware-user-progresses',
+ 'files',
+ 'file-refs',
+ 'folders',
+ 'users',
+ ],
+ httpClient,
+ }),
+ },
+});
+```
+
+Im obigen Code machen wir u.a. die JSONAPI-Schemata "courses", "users", "files" usw. bekannt. Was können wir jetzt damit anstellen?
+
+### Auslesen
+
+Vielleicht wollen wir gerne alle Nutzer auslesen und in einer Vue.js-Komponente ausgeben:
+
+```php
+<template>
+<div>
+ <ul>
+ <li v-for="user in allUsers" :key="user.id">
+ {{ user.attributes['formatted-name'] }}
+ </li>
+ </ul>
+</div>
+</template>
+
+<script>
+import { mapActions, mapGetters } from 'vuex';
+
+export default {
+ name: 'users-list',
+ mounted() {
+ this.loadAllUsers();
+ },
+ methods: {
+ ...mapActions({
+ loadAllUsers: 'users/loadAll',
+ }),
+ },
+ computed: {
+ ...mapGetters({
+ allUsers: 'users/all',
+ }),
+ },
+};
+</script>
+```
+
+ Das verschafft schon einen guten Überblick, was uns `reststate-vuex` bieten kann. Hier die wichtigsten Punkte:
+
+* Wir verwenden wie gewohnt `mapActions` und `mapGetters`, um Actions und Getter von `reststate-vuex` aufzurufen.
+* Die `loadAll`-Action lädt alle Daten des jeweiligen JSONAPI-Schemas und speichert diese im Store.
+* Wir verwenden den `all`-Getter, um alle Ressourcen eines JSONAPI-Schemas aus dem Store zu erhalten.
+* Der Zugriff auf eine `user`-Ressource erfolgt dann erwartungsgemäß mit `user.id` oder im `user.attributes`-Objekts.
+
+Einzelne Ressourcen können über die Action `users/loadById` aus dem JSONAPI-Backend in den Store geladen und über den Getter `users/byId` aus dem Store geholt werden.
+
+### Ladefortschritt und Fehler
+
+Damit wir nicht immer wieder Ladefortschritt- und Fehlerbehandlung implementieren müssen, sind nur wenige Änderungen notwendig. Zunächst ergänzen wir die vorhandenen Getter:
+
+```javascript
+...mapGetters({
++ isLoading: 'users/isLoading',
++ isError: 'users/isError',
+ allUsers: 'users/all',
+ })
+```
+
+und können dann auf diese im Template zugreifen:
+
+```php
+<template>
+ <div>
+- <ul>
++ <p v-if="isLoading" v-translate>Laden...</p>
++ <p v-else-if="isError" v-translate>Fehler beim Laden.</p>
++ <ul v-else>
+ <li
+ v-for="user in allUsers"
+```
+
+### Anlegen von Ressourcen
+
+Mit `reststate-vuex` können wir:
+
+* Ressourcen im JSONAPI-Backend via `axios` anlegen
+* und diese natürlich auch im `vuex` Store zugänglich machen
+
+Dazu ergänzen wir nur eine Action. Hier ein Beispiel für die Courseware, um Blöcke zu erstellen:
+
+```javascript
+methods: {
+ ...mapActions({
++ createBlock: 'courseware-blocks/create',
+ }),
+```
+
+Diese Action können wir jetzt im JavaScript-Code verwenden:
+
+```javascript
+// Der `container` stammt aus dem Store, aber das geht auch per Hand.
+const container = { type: 'courseware-containers', id: '17' };
+
+// Wir erstellen eine JSON-Repräsentation eines Courseware-Blocks:
+// - mit einem Beispiel-Block-Type.
+// - ohne `payload`
+// - mit Verknüpfung zu einem Courseware-Container
+const block = {
+ attributes: {
+ 'block-type': 'text',
+ 'payload': null,
+ },
+ relationships: {
+ container: {
+ data: { type: container.type, id: container.id },
+ },
+ },
+};
+
+this.createBlock('courseware-blocks/create', block);
+```
+
+
+### Löschen von Ressourcen
+
+Um Ressourcen im JSONAPI-Backend und im `vuex`-Store zu löschen, verwenden wir einfach die entsprechende Action:
+
+```javascript
+methods: {
+ ...mapActions({
++ deleteBlock: 'courseware-blocks/delete',
+ }),
+```
+
+und verwenden diese Action dann einfach in unsere Vue.js-Komponente:
+
+```php
++ <button @click="deleteBlock(block)" v-translate>
++ Block löschen
++ </button>
+```
+
+### Ändern von Ressourcen
+
+Auch für das Ändern von Ressourcen im JSONAPI-Backend und im `vuex`-Store gibt es eine entsprechende Action:
+
+```javascript
+methods: {
+ ...mapActions({
++ updateBlock: 'courseware-blocks/update',
+ }),
+```
+
+und verwenden diese Action im JavaScript-Code
+
+```javascript
+const block = this.getBlock({ id: '17' });
+block.attributes.payload = { foo: 'bar' };
+this.updateBlock(block);
+```
+
+Da der `vuex` Store reaktiv ist, ändern sich dadurch wie gewohnt alle Komponenten, die mit dieser Ressource zusammenarbeiten und natürlich auch im JSONAPI-Backend.
+
+### Mehr
+
+Die `reststate-vuex`-Bibliothek bietet noch viele andere Möglichkeiten, die von der JSONAPI angeboten werden, gut dokumentiert unter https://vuex.reststate.codingitwrong.com/
diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js
new file mode 100644
index 0000000..cd5665c
--- /dev/null
+++ b/docs/docusaurus.config.js
@@ -0,0 +1,88 @@
+/** @type {import('@docusaurus/types').DocusaurusConfig} */
+module.exports = {
+ title: 'Stud.IP Entwicklung',
+ tagline: 'Dokumentation für die Entwicklung von Stud.IP',
+ url: 'https://docs.gitlab.studip.de',
+ baseUrl: '/entwicklung/',
+ onBrokenLinks: 'warn',
+ onBrokenMarkdownLinks: 'warn',
+ favicon: 'img/favicon.ico',
+ organizationName: 'Stud.IP',
+ projectName: 'entwicklung',
+ favicon: 'https://www.studip.de/favicon.ico',
+ trailingSlash: false,
+ markdown: {
+ mermaid: true,
+ },
+ i18n: {
+ defaultLocale: 'de',
+ locales: ['de'],
+ },
+ themeConfig: {
+ prism: {
+ additionalLanguages: ['php', 'sass'],
+ },
+ navbar: {
+ logo: {
+ alt: 'Stud.IP Entwicklung',
+ src: 'img/studip-hilfe.png',
+ },
+ items: [
+ {
+ to: 'docs/quickstart/',
+ activeBasePath: 'docs/quickstart',
+ label: 'Quickstart',
+ position: 'left',
+ },
+ {
+ to: 'docs/start',
+ activeBasePath: 'docs/start',
+ label: 'Dokumentation',
+ position: 'left',
+ },
+ {
+ to: 'docs/rules/introduction',
+ activeBasePath: 'docs/rules/introduction',
+ label: 'Organisation',
+ position: 'left',
+ },
+ {
+ href: 'https://docs.gitlab.studip.de/api',
+ label: 'API',
+ position: 'right',
+ },
+ {
+ href: 'https://gitlab.studip.de',
+ label: 'Stud.IP GitLab',
+ position: 'right',
+ },
+ ],
+ }
+ },
+ presets: [
+ [
+ '@docusaurus/preset-classic',
+ {
+ docs: {
+ sidebarPath: require.resolve('./sidebars.js'),
+ // Please change this to your repo.
+ editUrl: 'https://gitlab.studip.de/docs/entwicklung/-/tree/main/website',
+ showLastUpdateTime: true,
+ showLastUpdateAuthor: true,
+ },
+ theme: {
+ customCss: require.resolve('./src/css/custom.css'),
+ },
+ },
+ ],
+ ],
+ plugins: [
+ [
+ require.resolve("@cmfcmf/docusaurus-search-local"),
+ {
+ language: 'de'// Options here
+ },
+ ],
+ ],
+ themes: ['@docusaurus/theme-mermaid'],
+};
diff --git a/docs/i18n/de/code.json b/docs/i18n/de/code.json
new file mode 100644
index 0000000..65f4145
--- /dev/null
+++ b/docs/i18n/de/code.json
@@ -0,0 +1,293 @@
+{
+ "theme.ErrorPageContent.title": {
+ "message": "Die Seite ist abgestürzt.",
+ "description": "The title of the fallback page when the page crashed"
+ },
+ "theme.NotFound.title": {
+ "message": "Seite nicht gefunden",
+ "description": "The title of the 404 page"
+ },
+ "theme.NotFound.p1": {
+ "message": "Wir konnten nicht finden, wonach Sie gesucht haben.",
+ "description": "The first paragraph of the 404 page"
+ },
+ "theme.NotFound.p2": {
+ "message": "Bitte kontaktieren Sie den Besitzer der Seite, die Sie mit der ursprünglichen URL verlinkt hat, und teilen Sie ihm mit, dass der Link nicht mehr funktioniert.",
+ "description": "The 2nd paragraph of the 404 page"
+ },
+ "theme.admonition.note": {
+ "message": "note",
+ "description": "The default label used for the Note admonition (:::note)"
+ },
+ "theme.admonition.tip": {
+ "message": "tip",
+ "description": "The default label used for the Tip admonition (:::tip)"
+ },
+ "theme.admonition.danger": {
+ "message": "danger",
+ "description": "The default label used for the Danger admonition (:::danger)"
+ },
+ "theme.admonition.info": {
+ "message": "info",
+ "description": "The default label used for the Info admonition (:::info)"
+ },
+ "theme.admonition.caution": {
+ "message": "caution",
+ "description": "The default label used for the Caution admonition (:::caution)"
+ },
+ "theme.blog.archive.title": {
+ "message": "Archiv",
+ "description": "The page & hero title of the blog archive page"
+ },
+ "theme.blog.archive.description": {
+ "message": "Archiv",
+ "description": "The page & hero description of the blog archive page"
+ },
+ "theme.BackToTopButton.buttonAriaLabel": {
+ "message": "Zurück nach oben scrollen",
+ "description": "The ARIA label for the back to top button"
+ },
+ "theme.blog.paginator.navAriaLabel": {
+ "message": "Navigation der Blog-Listenseite",
+ "description": "The ARIA label for the blog pagination"
+ },
+ "theme.blog.paginator.newerEntries": {
+ "message": "Neuere Einträge",
+ "description": "The label used to navigate to the newer blog posts page (previous page)"
+ },
+ "theme.blog.paginator.olderEntries": {
+ "message": "Ältere Einträge",
+ "description": "The label used to navigate to the older blog posts page (next page)"
+ },
+ "theme.blog.post.paginator.navAriaLabel": {
+ "message": "Blog Post Seiten Navigation",
+ "description": "The ARIA label for the blog posts pagination"
+ },
+ "theme.blog.post.paginator.newerPost": {
+ "message": "Neuer Post",
+ "description": "The blog post button label to navigate to the newer/previous post"
+ },
+ "theme.blog.post.paginator.olderPost": {
+ "message": "Älterer Post",
+ "description": "The blog post button label to navigate to the older/next post"
+ },
+ "theme.blog.post.plurals": {
+ "message": "Ein Post|{count} Posts",
+ "description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
+ },
+ "theme.blog.tagTitle": {
+ "message": "{nPosts} getaggt mit \"{tagName}\"",
+ "description": "The title of the page for a blog tag"
+ },
+ "theme.tags.tagsPageLink": {
+ "message": "Alle Tags anzeigen",
+ "description": "The label of the link targeting the tag list page"
+ },
+ "theme.colorToggle.ariaLabel": {
+ "message": "Umschalten zwischen dunkler und heller Ansicht (momentan {mode})",
+ "description": "The ARIA label for the navbar color mode toggle"
+ },
+ "theme.colorToggle.ariaLabel.mode.dark": {
+ "message": "dunkler Modus",
+ "description": "The name for the dark color mode"
+ },
+ "theme.colorToggle.ariaLabel.mode.light": {
+ "message": "heller Modus",
+ "description": "The name for the light color mode"
+ },
+ "theme.docs.breadcrumbs.navAriaLabel": {
+ "message": "Breadcrumbs",
+ "description": "The ARIA label for the breadcrumbs"
+ },
+ "theme.docs.DocCard.categoryDescription": {
+ "message": "{count} Einträge",
+ "description": "The default description for a category card in the generated index about how many items this category includes"
+ },
+ "theme.docs.paginator.navAriaLabel": {
+ "message": "Dokumentation Seiten",
+ "description": "The ARIA label for the docs pagination"
+ },
+ "theme.docs.paginator.previous": {
+ "message": "Zurück",
+ "description": "The label used to navigate to the previous doc"
+ },
+ "theme.docs.paginator.next": {
+ "message": "Weiter",
+ "description": "The label used to navigate to the next doc"
+ },
+ "theme.docs.tagDocListPageTitle.nDocsTagged": {
+ "message": "Ein doc getaggt|{count} docs getaggt",
+ "description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
+ },
+ "theme.docs.tagDocListPageTitle": {
+ "message": "{nDocsTagged} mit \"{tagName}\"",
+ "description": "The title of the page for a docs tag"
+ },
+ "theme.docs.versionBadge.label": {
+ "message": "Version: {versionLabel}"
+ },
+ "theme.docs.versions.unreleasedVersionLabel": {
+ "message": "Das ist die unveröffentlichte Dokumentation für {siteTitle} {versionLabel}.",
+ "description": "The label used to tell the user that he's browsing an unreleased doc version"
+ },
+ "theme.docs.versions.unmaintainedVersionLabel": {
+ "message": "Das ist die Dokumentation für {siteTitle} {versionLabel} und wird nicht weiter gewartet.",
+ "description": "The label used to tell the user that he's browsing an unmaintained doc version"
+ },
+ "theme.docs.versions.latestVersionSuggestionLabel": {
+ "message": "Für die aktuellste Dokumentation bitte auf {latestVersionLink} ({versionLabel}) gehen.",
+ "description": "The label used to tell the user to check the latest version"
+ },
+ "theme.docs.versions.latestVersionLinkLabel": {
+ "message": "letzte Version",
+ "description": "The label used for the latest version suggestion link label"
+ },
+ "theme.common.editThisPage": {
+ "message": "Diese Seite bearbeiten",
+ "description": "The link label to edit the current page"
+ },
+ "theme.common.headingLinkTitle": {
+ "message": "Direkter Link zur {heading}",
+ "description": "Title for link to heading"
+ },
+ "theme.lastUpdated.atDate": {
+ "message": " am {date}",
+ "description": "The words used to describe on which date a page has been last updated"
+ },
+ "theme.lastUpdated.byUser": {
+ "message": " von {user}",
+ "description": "The words used to describe by who the page has been last updated"
+ },
+ "theme.lastUpdated.lastUpdatedAtBy": {
+ "message": "Letztes Update{atDate}{byUser}",
+ "description": "The sentence used to display when a page has been last updated, and by who"
+ },
+ "theme.navbar.mobileVersionsDropdown.label": {
+ "message": "Versionen",
+ "description": "The label for the navbar versions dropdown on mobile view"
+ },
+ "theme.tags.tagsListLabel": {
+ "message": "Tags:",
+ "description": "The label alongside a tag list"
+ },
+ "theme.AnnouncementBar.closeButtonAriaLabel": {
+ "message": "Schließen",
+ "description": "The ARIA label for close button of announcement bar"
+ },
+ "theme.blog.sidebar.navAriaLabel": {
+ "message": "Navigation der letzten Beiträge im Blog",
+ "description": "The ARIA label for recent posts in the blog sidebar"
+ },
+ "theme.CodeBlock.copied": {
+ "message": "Kopiert",
+ "description": "The copied button label on code blocks"
+ },
+ "theme.CodeBlock.copyButtonAriaLabel": {
+ "message": "In die Zwischenablage kopieren",
+ "description": "The ARIA label for copy code blocks button"
+ },
+ "theme.CodeBlock.copy": {
+ "message": "Kopieren",
+ "description": "The copy button label on code blocks"
+ },
+ "theme.CodeBlock.wordWrapToggle": {
+ "message": "Toggle word wrap",
+ "description": "The title attribute for toggle word wrapping button of code block lines"
+ },
+ "theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": {
+ "message": "Umschalten der Seitenleiste mit einklappbarer Kategorie '{label}'",
+ "description": "The ARIA label to toggle the collapsible sidebar category"
+ },
+ "theme.NavBar.navAriaLabel": {
+ "message": "Main",
+ "description": "The ARIA label for the main navigation"
+ },
+ "theme.navbar.mobileLanguageDropdown.label": {
+ "message": "Sprachen",
+ "description": "The label for the mobile language switcher dropdown"
+ },
+ "theme.TOCCollapsible.toggleButtonLabel": {
+ "message": "Auf dieser Seite",
+ "description": "The label used by the button on the collapsible TOC component"
+ },
+ "theme.blog.post.readingTime.plurals": {
+ "message": "Eine Minute Lesezeit|{readingTime} Minuten Lesezeit",
+ "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
+ },
+ "theme.docs.breadcrumbs.home": {
+ "message": "Home page",
+ "description": "The ARIA label for the home page in the breadcrumbs"
+ },
+ "theme.blog.post.readMore": {
+ "message": "Mehr lesen",
+ "description": "The label used in blog post item excerpts to link to full blog posts"
+ },
+ "theme.blog.post.readMoreLabel": {
+ "message": "Mehr lesen über {title}",
+ "description": "The ARIA label for the link to full blog posts from excerpts"
+ },
+ "theme.docs.sidebar.navAriaLabel": {
+ "message": "Docs sidebar",
+ "description": "The ARIA label for the sidebar navigation"
+ },
+ "theme.docs.sidebar.collapseButtonTitle": {
+ "message": "Seitenleiste einklappen",
+ "description": "The title attribute for collapse button of doc sidebar"
+ },
+ "theme.docs.sidebar.collapseButtonAriaLabel": {
+ "message": "Seitenleiste einklappen",
+ "description": "The title attribute for collapse button of doc sidebar"
+ },
+ "theme.docs.sidebar.closeSidebarButtonAriaLabel": {
+ "message": "Close navigation bar",
+ "description": "The ARIA label for close button of mobile sidebar"
+ },
+ "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": {
+ "message": "← Zurück zum Hauptmenü",
+ "description": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)"
+ },
+ "theme.docs.sidebar.toggleSidebarButtonAriaLabel": {
+ "message": "Toggle navigation bar",
+ "description": "The ARIA label for hamburger menu button of mobile navigation"
+ },
+ "theme.docs.sidebar.expandButtonTitle": {
+ "message": "Seitenleiste ausklappen",
+ "description": "The ARIA label and title attribute for expand button of doc sidebar"
+ },
+ "theme.docs.sidebar.expandButtonAriaLabel": {
+ "message": "Seitenleiste ausklappen",
+ "description": "The ARIA label and title attribute for expand button of doc sidebar"
+ },
+ "cmfcmf/d-s-l.searchBar.placeholder": {
+ "message": "Suchen...",
+ "description": "Placeholder shown in the searchbar"
+ },
+ "cmfcmf/d-s-l.searchBar.clearButtonTitle": {
+ "message": "Leeren",
+ "description": "Title of the button to clear the current search input"
+ },
+ "cmfcmf/d-s-l.searchBar.detachedCancelButtonText": {
+ "message": "Abbrechen",
+ "description": "Text of the button to close the detached search window"
+ },
+ "cmfcmf/d-s-l.searchBar.submitButtonTitle": {
+ "message": "Suchen",
+ "description": "Title of the button to submit a new search"
+ },
+ "cmfcmf/d-s-l.searchBar.noResults": {
+ "message": "Keine Suchergebnissse gefunden.",
+ "description": "message shown if no results are found"
+ },
+ "theme.ErrorPageContent.tryAgain": {
+ "message": "Nochmal versuchen",
+ "description": "The label of the button to try again rendering when the React error boundary captures an error"
+ },
+ "theme.common.skipToMainContent": {
+ "message": "Zum Hauptinhalt springen",
+ "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation"
+ },
+ "theme.tags.tagsPageTitle": {
+ "message": "Tags",
+ "description": "The title of the tag list page"
+ }
+}
diff --git a/docs/i18n/de/docusaurus-plugin-content-blog/options.json b/docs/i18n/de/docusaurus-plugin-content-blog/options.json
new file mode 100644
index 0000000..9239ff7
--- /dev/null
+++ b/docs/i18n/de/docusaurus-plugin-content-blog/options.json
@@ -0,0 +1,14 @@
+{
+ "title": {
+ "message": "Blog",
+ "description": "The title for the blog used in SEO"
+ },
+ "description": {
+ "message": "Blog",
+ "description": "The description for the blog used in SEO"
+ },
+ "sidebar.title": {
+ "message": "Recent posts",
+ "description": "The label for the left sidebar"
+ }
+}
diff --git a/docs/i18n/de/docusaurus-plugin-content-docs/current.json b/docs/i18n/de/docusaurus-plugin-content-docs/current.json
new file mode 100644
index 0000000..fbb551f
--- /dev/null
+++ b/docs/i18n/de/docusaurus-plugin-content-docs/current.json
@@ -0,0 +1,54 @@
+{
+ "version.label": {
+ "message": "Next",
+ "description": "The label for version current"
+ },
+ "sidebar.docs.category.Plugins": {
+ "message": "Plugins",
+ "description": "The label for category Plugins in sidebar docs"
+ },
+ "sidebar.docs.category.Visual Style Guide": {
+ "message": "Visual Style Guide",
+ "description": "The label for category Visual Style Guide in sidebar docs"
+ },
+ "sidebar.docs.category.JSON:API": {
+ "message": "JSON:API",
+ "description": "The label for category JSON:API in sidebar docs"
+ },
+ "sidebar.docs.category.REST-API": {
+ "message": "REST-API",
+ "description": "The label for category REST-API in sidebar docs"
+ },
+ "sidebar.docs.category.Barrierefreiheit": {
+ "message": "Barrierefreiheit",
+ "description": "The label for category Barrierefreiheit in sidebar docs"
+ },
+ "sidebar.docs.category.Exporte": {
+ "message": "Exporte",
+ "description": "The label for category Exporte in sidebar docs"
+ },
+ "sidebar.docs.category.Testing": {
+ "message": "Testing",
+ "description": "The label for category Testing in sidebar docs"
+ },
+ "sidebar.docs.category.Vue.js": {
+ "message": "Vue.js",
+ "description": "The label for category Vue.js in sidebar docs"
+ },
+ "sidebar.docs.category.Funktionen": {
+ "message": "Funktionen",
+ "description": "The label for category Funktionen in sidebar docs"
+ },
+ "sidebar.docs.link.Erstellung eines Plugins": {
+ "message": "Erstellung eines Plugins",
+ "description": "The label for link Erstellung eines Plugins in sidebar docs, linking to https://develop.studip.de/studip/dispatch.php/document/download/5747961f81b385b1520cf7dc393f1db6"
+ },
+ "sidebar.quickstartSidebar.category.Quickstart": {
+ "message": "Quickstart",
+ "description": "The label for category Quickstart in sidebar quickstartSidebar"
+ },
+ "sidebar.rulesSidebar.category.Allgemeines": {
+ "message": "Allgemeines",
+ "description": "The label for category Allgemeines in sidebar rulesSidebar"
+ }
+}
diff --git a/docs/i18n/de/docusaurus-theme-classic/footer.json b/docs/i18n/de/docusaurus-theme-classic/footer.json
new file mode 100644
index 0000000..48d1287
--- /dev/null
+++ b/docs/i18n/de/docusaurus-theme-classic/footer.json
@@ -0,0 +1,34 @@
+{
+ "link.title.Dokumentation": {
+ "message": "Dokumentation",
+ "description": "The title of the footer links column with title=Dokumentation in the footer"
+ },
+ "link.title.Community": {
+ "message": "Community",
+ "description": "The title of the footer links column with title=Community in the footer"
+ },
+ "link.title.Mehr": {
+ "message": "Mehr",
+ "description": "The title of the footer links column with title=Mehr in the footer"
+ },
+ "link.item.label.Getting Started": {
+ "message": "Getting Started",
+ "description": "The label of footer link with label=Getting Started linking to docs/start"
+ },
+ "link.item.label.API Dokumentation": {
+ "message": "API Dokumentation",
+ "description": "The label of footer link with label=API Dokumentation linking to https://docs.gitlab.studip.de/api/"
+ },
+ "link.item.label.Developer-Board": {
+ "message": "Developer-Board",
+ "description": "The label of footer link with label=Developer-Board linking to https://develop.studip.de"
+ },
+ "link.item.label.GitLab": {
+ "message": "GitLab",
+ "description": "The label of footer link with label=GitLab linking to https://gitlab.studip.de"
+ },
+ "copyright": {
+ "message": "Copyright © 2023 Stud.IP – Built with Docusaurus.",
+ "description": "The footer copyright"
+ }
+}
diff --git a/docs/i18n/de/docusaurus-theme-classic/navbar.json b/docs/i18n/de/docusaurus-theme-classic/navbar.json
new file mode 100644
index 0000000..966ed21
--- /dev/null
+++ b/docs/i18n/de/docusaurus-theme-classic/navbar.json
@@ -0,0 +1,26 @@
+{
+ "logo.alt": {
+ "message": "Stud.IP Entwicklung",
+ "description": "The alt text of navbar logo"
+ },
+ "item.label.Quickstart": {
+ "message": "Quickstart",
+ "description": "Navbar item with label Quickstart"
+ },
+ "item.label.Dokumentation": {
+ "message": "Dokumentation",
+ "description": "Navbar item with label Dokumentation"
+ },
+ "item.label.Organisation": {
+ "message": "Organisation",
+ "description": "Navbar item with label Organisation"
+ },
+ "item.label.API": {
+ "message": "API",
+ "description": "Navbar item with label API"
+ },
+ "item.label.Stud.IP GitLab": {
+ "message": "Stud.IP GitLab",
+ "description": "Navbar item with label Stud.IP GitLab"
+ }
+}
diff --git a/docs/package.json b/docs/package.json
new file mode 100644
index 0000000..6980e99
--- /dev/null
+++ b/docs/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "website",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "docusaurus": "docusaurus",
+ "start": "docusaurus start",
+ "build": "docusaurus build",
+ "swizzle": "docusaurus swizzle",
+ "deploy": "docusaurus deploy",
+ "clear": "docusaurus clear",
+ "serve": "docusaurus serve",
+ "write-translations": "docusaurus write-translations",
+ "write-heading-ids": "docusaurus write-heading-ids"
+ },
+ "dependencies": {
+ "@cmfcmf/docusaurus-search-local": "^1.1.0",
+ "@docusaurus/core": "^2.3.1",
+ "@docusaurus/preset-classic": "^2.3.1",
+ "@docusaurus/theme-mermaid": "^2.4.3",
+ "@mdx-js/react": "^1.6.22",
+ "clsx": "^1.2.1",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "browserslist": {
+ "production": [
+ ">0.5%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ }
+}
diff --git a/docs/sidebars.js b/docs/sidebars.js
new file mode 100644
index 0000000..923d69a
--- /dev/null
+++ b/docs/sidebars.js
@@ -0,0 +1,203 @@
+module.exports = {
+ docs: [
+ {
+ type: "doc",
+ id: "start",
+ },
+ {
+ type: "category",
+ label: "Plugins",
+ items: [
+ "plugins/plugins",
+ "plugins/tutorial",
+ "plugins/tutorial-struktur",
+ "plugins/tutorial-funktion",
+ "plugins/automatic-updates",
+ {
+ type: "link",
+ label: "Erstellung eines Plugins",
+ href: "https://develop.studip.de/studip/dispatch.php/document/download/5747961f81b385b1520cf7dc393f1db6",
+ },
+ ],
+ },
+ {
+ type: "doc",
+ id: "coding-style",
+ },
+ {
+ type: "category",
+ label: "Visual Style Guide",
+ items: [
+ "visual-style-guide/einleitung",
+ "visual-style-guide/seitenaufbau",
+ "visual-style-guide/navigation",
+ "visual-style-guide/inhaltselemente",
+ "visual-style-guide/elementlisten",
+ "visual-style-guide/design",
+ "visual-style-guide/fonts",
+ "visual-style-guide/css",
+ "visual-style-guide/dialoge",
+ "visual-style-guide/logos",
+ "visual-style-guide/icons",
+ "visual-style-guide/formulare",
+ "visual-style-guide/sprache",
+ "visual-style-guide/suche",
+ ],
+ },
+ {
+ type: "category",
+ label: "JSON:API",
+ items: [
+ "jsonapi/start",
+ "jsonapi/routen",
+ "jsonapi/errors",
+ "jsonapi/users",
+ "jsonapi/activitystreams",
+ "jsonapi/news",
+ "jsonapi/blubber",
+ "jsonapi/files",
+ "jsonapi/forum",
+ "jsonapi/institutes",
+ "jsonapi/contacts",
+ "jsonapi/messages",
+ "jsonapi/planer",
+ "jsonapi/semesters",
+ "jsonapi/courses",
+ "jsonapi/wiki",
+ "jsonapi/discovery",
+ "jsonapi/studip",
+ ],
+ },
+ {
+ type: "category",
+ label: "REST-API",
+ items: [
+ 'restapi/index'
+ ]
+ },
+ {
+ type: "category",
+ label: "Barrierefreiheit",
+ items: [
+ "a11y/start",
+ "a11y/background",
+ "a11y/tips",
+ "a11y/review",
+ "a11y/declaration_template",
+ "a11y/todo",
+ "a11y/skiplinks",
+ ]
+ },
+ {
+ type: "category",
+ label: "Testing",
+ items: [
+ "testing/codeception",
+ "testing/e2e",
+ ],
+ },
+ {
+ type: "category",
+ label: "Vue.js",
+ items: [
+ "vuejs/start",
+ "vuejs/vuex"
+ ]
+ },
+ {
+ type: "category",
+ label: "Funktionen",
+ items: [
+ "functions/activity-api",
+ "functions/admin-search",
+ "functions/actionmenu",
+ "functions/course-wizard",
+ "functions/coursesets",
+ "functions/assets",
+ "functions/baumstrukturen",
+ "functions/user-lookup",
+ "functions/cli",
+ "functions/cronjobs",
+ "functions/css-z-indizes",
+ "functions/etask-tables",
+ "functions/event-logging",
+ "functions/format",
+ "functions/new-html-structure",
+ "functions/icon",
+ "functions/less",
+ "functions/log",
+ "functions/migrations",
+ "functions/modaler-dialog",
+ "functions/global-search-module",
+ "functions/multi-person-search",
+ "functions/notifications",
+ "functions/oauth2",
+ "functions/page-layout",
+ "functions/pdf-exports",
+ "functions/jsupdater",
+ "functions/personal-notifications",
+ "functions/cli-plugin-manager",
+ "functions/qr-codes",
+ "functions/quick-search",
+ "functions/responsive-navigation",
+ "functions/cache",
+ "functions/studip-form",
+ "functions/studip-mail",
+ "functions/studip-format",
+ "functions/studip-pdo",
+ "functions/deputy",
+ "functions/visibility",
+ "functions/wysiwyg",
+ ]
+ }
+ ],
+ quickstartSidebar: [
+ {
+ type: "category",
+ label: "Quickstart",
+ items: [
+ "quickstart/ueberblick",
+ "quickstart/einsteiger",
+ "quickstart/entwicklungsumgebung",
+ "quickstart/ordnerstruktur",
+ "quickstart/dateitypen",
+ "quickstart/api-dokumentation",
+ "quickstart/allgemeine-struktur",
+ "quickstart/datenbank",
+ "quickstart/simpleormap",
+ "quickstart/html-ausgaben",
+ "quickstart/templates",
+ "quickstart/helpbar",
+ "quickstart/trails",
+ "quickstart/message-box",
+ "quickstart/navigation",
+ "quickstart/internationalisierung",
+ "quickstart/rechtestufen",
+ "quickstart/asset-bundling",
+ "quickstart/javascript",
+ "quickstart/responsive-design",
+ "quickstart/utf8",
+ "quickstart/users",
+ "quickstart/cheat-sheet",
+ "quickstart/troubleshooting",
+ "quickstart/buttons",
+ "quickstart/csrf-protection",
+ "quickstart/db-klassen",
+ "quickstart/request",
+ "quickstart/sidebar",
+ "quickstart/url-helper",
+ ],
+ },
+ ],
+ rulesSidebar: [
+ {
+ type: "category",
+ label: "Allgemeines",
+ items: [
+ "rules/introduction",
+ "rules/gitlab-workflows",
+ "rules/fehler-melden",
+ ],
+ },
+ ]
+};
diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css
new file mode 100644
index 0000000..1e11fe5
--- /dev/null
+++ b/docs/src/css/custom.css
@@ -0,0 +1,53 @@
+/* stylelint-disable docusaurus/copyright-header */
+/**
+ * Any CSS included here will be global. The classic template
+ * bundles Infima by default. Infima is a CSS framework designed to
+ * work well for content-centric websites.
+ */
+
+/* You can override the default Infima variables here. */
+:root {
+ --ifm-color-primary: #28497c;
+ --ifm-color-primary-dark: #28497CFF;
+ --ifm-color-primary-darker: #28497c;
+ --ifm-color-primary-darkest: #1f3f70;
+ --ifm-color-primary-light: #7e92b0;
+ --ifm-color-primary-lighter: #a9b6cb;
+ --ifm-color-primary-lightest: #d4dbe5;
+ --ifm-code-font-size: 95%;
+ --base-color: #28497c;
+ --content-color-20: #e7ebf1;
+}
+
+[data-theme='dark'] {
+ --ifm-color-primary: #7e92b0;
+ --ifm-color-primary-dark: #a9b6cb;
+ --ifm-color-primary-darker: #d4dbe5;
+ --ifm-color-primary-darkest: #536d96;
+ --ifm-color-primary-light: #36598f;
+ --ifm-color-primary-lighter: #28497c;
+ --ifm-color-primary-lightest: #1f3f70;
+}
+
+.docusaurus-highlight-code-line {
+ background-color: #d60000;
+ display: block;
+ margin: 0 calc(-1 * var(--ifm-pre-padding));
+ padding: 0 var(--ifm-pre-padding);
+}
+
+a {
+ color: var(--base-color);
+}
+
+.main-entry-button {
+ color: var(--content-color-20) !important;
+}
+
+.main-entry-button:hover, .main-entry-button:active {
+ color: var(--ifm-color-gray-900) !important;
+}
+
+.landing-page-box img {
+ width: 120px;
+}
diff --git a/docs/src/pages/index.js b/docs/src/pages/index.js
new file mode 100644
index 0000000..203c0df
--- /dev/null
+++ b/docs/src/pages/index.js
@@ -0,0 +1,101 @@
+import React from 'react';
+import clsx from 'clsx';
+import Layout from '@theme/Layout';
+import Link from '@docusaurus/Link';
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+import useBaseUrl from '@docusaurus/useBaseUrl';
+import styles from './styles.module.css';
+
+const features = [
+ {
+ title: 'Fehler / BIEST berichten',
+ imageUrl: 'https://develop.studip.de/studip/assets/images/icons/blue/exclaim-circle-full.svg',
+ url: 'https://gitlab.studip.de/studip/studip/-/issues/new',
+ description: (
+ <>
+ Haben Sie einen Fehler gefunden? Dann melden Sie diesen hier
+ </>
+ ),
+ target: '__blank'
+ },
+ {
+ title: 'Kontakt zur Community',
+ imageUrl: 'https://develop.studip.de/studip/assets/images/icons/blue/community.svg',
+ url: 'https://develop.studip.de/',
+ description: (
+ <>
+ Kommen Sie und werden Sie ein Teil der Community
+ </>
+ ),
+ target: '__blank'
+ },
+ {
+ title: 'Entwicklungs-Chat',
+ imageUrl: 'https://develop.studip.de/studip/assets/images/icons/blue/chat.svg',
+ url: 'https://matrix.to/#/%23Stud.IP:matrix.org',
+ description: (
+ <>
+ Hier bekommen Sie schnell und unkompliziert Hilfe
+ </>
+ ),
+ target: '__blank'
+ },
+];
+
+function Feature({imageUrl, title, description, url, target}) {
+ const imgUrl = useBaseUrl(imageUrl);
+ return (
+ <div className={clsx('col col--4 landing-page-box', styles.feature)}>
+ <a href={url} target={target}>
+ {imgUrl && (
+ <div className="text--center">
+ <img className={styles.featureImage} src={imgUrl} alt={title} />
+ </div>
+ )}
+ <h3 className="text--center">{title}</h3>
+ <p className="text--center">{description}</p>
+ </a>
+ </div>
+ );
+}
+import {Redirect} from '@docusaurus/router';
+
+export default function Home() {
+ const context = useDocusaurusContext();
+ const {siteConfig = {}} = context;
+ return (
+ <Layout
+ title={`${siteConfig.title}`}
+ description="Description will go into a meta tag in <head />">
+ <header className={clsx('hero hero--primary', styles.heroBanner)}>
+ <div className="container">
+ <h1 className="hero__title">{siteConfig.title}</h1>
+ <p className="hero__subtitle">{siteConfig.tagline}</p>
+ <div className={styles.buttons}>
+ <Link
+ className={clsx(
+ 'button button--outline button--secondary button--lg main-entry-button',
+ styles.getStarted,
+ )}
+ to={useBaseUrl('docs/quickstart')}>
+ Zur Dokumentation
+ </Link>
+ </div>
+ </div>
+ </header>
+ <main>
+ {features && features.length > 0 && (
+ <section className={styles.features}>
+ <div className="container">
+ <div className="row">
+ {features.map((props, idx) => (
+ <Feature key={idx} {...props} />
+ ))}
+ </div>
+ </div>
+ </section>
+ )}
+ </main>
+ </Layout>
+ );
+}
diff --git a/docs/src/pages/markdown-page.md b/docs/src/pages/markdown-page.md
new file mode 100644
index 0000000..9756c5b
--- /dev/null
+++ b/docs/src/pages/markdown-page.md
@@ -0,0 +1,7 @@
+---
+title: Markdown page example
+---
+
+# Markdown page example
+
+You don't need React to write simple standalone pages.
diff --git a/docs/src/pages/styles.module.css b/docs/src/pages/styles.module.css
new file mode 100644
index 0000000..c1aa851
--- /dev/null
+++ b/docs/src/pages/styles.module.css
@@ -0,0 +1,37 @@
+/* stylelint-disable docusaurus/copyright-header */
+
+/**
+ * CSS files with the .module.css suffix will be treated as CSS modules
+ * and scoped locally.
+ */
+
+.heroBanner {
+ padding: 4rem 0;
+ text-align: center;
+ position: relative;
+ overflow: hidden;
+}
+
+@media screen and (max-width: 966px) {
+ .heroBanner {
+ padding: 2rem;
+ }
+}
+
+.buttons {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.features {
+ display: flex;
+ align-items: center;
+ padding: 2rem 0;
+ width: 100%;
+}
+
+.featureImage {
+ height: 200px;
+ width: 200px;
+}
diff --git a/docs/static/.nojekyll b/docs/static/.nojekyll
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/docs/static/.nojekyll
diff --git a/docs/static/img/docusaurus.png b/docs/static/img/docusaurus.png
new file mode 100644
index 0000000..f458149
--- /dev/null
+++ b/docs/static/img/docusaurus.png
Binary files differ
diff --git a/docs/static/img/favicon.ico b/docs/static/img/favicon.ico
new file mode 100644
index 0000000..c01d54b
--- /dev/null
+++ b/docs/static/img/favicon.ico
Binary files differ
diff --git a/docs/static/img/logo.svg b/docs/static/img/logo.svg
new file mode 100644
index 0000000..9db6d0d
--- /dev/null
+++ b/docs/static/img/logo.svg
@@ -0,0 +1 @@
+<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#FFF" d="M99 52h84v34H99z"/><path d="M23 163c-7.398 0-13.843-4.027-17.303-10A19.886 19.886 0 0 0 3 163c0 11.046 8.954 20 20 20h20v-20H23z" fill="#3ECC5F"/><path d="M112.98 57.376L183 53V43c0-11.046-8.954-20-20-20H73l-2.5-4.33c-1.112-1.925-3.889-1.925-5 0L63 23l-2.5-4.33c-1.111-1.925-3.889-1.925-5 0L53 23l-2.5-4.33c-1.111-1.925-3.889-1.925-5 0L43 23c-.022 0-.042.003-.065.003l-4.142-4.141c-1.57-1.571-4.252-.853-4.828 1.294l-1.369 5.104-5.192-1.392c-2.148-.575-4.111 1.389-3.535 3.536l1.39 5.193-5.102 1.367c-2.148.576-2.867 3.259-1.296 4.83l4.142 4.142c0 .021-.003.042-.003.064l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 53l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 63l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 73l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 83l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 93l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 103l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 113l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 123l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 133l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 143l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 153l-4.33 2.5c-1.925 1.111-1.925 3.889 0 5L23 163c0 11.046 8.954 20 20 20h120c11.046 0 20-8.954 20-20V83l-70.02-4.376A10.645 10.645 0 0 1 103 68c0-5.621 4.37-10.273 9.98-10.624" fill="#3ECC5F"/><path fill="#3ECC5F" d="M143 183h30v-40h-30z"/><path d="M193 158c-.219 0-.428.037-.639.064-.038-.15-.074-.301-.116-.451A5 5 0 0 0 190.32 148a4.96 4.96 0 0 0-3.016 1.036 26.531 26.531 0 0 0-.335-.336 4.955 4.955 0 0 0 1.011-2.987 5 5 0 0 0-9.599-1.959c-.148-.042-.297-.077-.445-.115.027-.211.064-.42.064-.639a5 5 0 0 0-5-5 5 5 0 0 0-5 5c0 .219.037.428.064.639-.148.038-.297.073-.445.115a4.998 4.998 0 0 0-9.599 1.959c0 1.125.384 2.151 1.011 2.987-3.717 3.632-6.031 8.693-6.031 14.3 0 11.046 8.954 20 20 20 9.339 0 17.16-6.41 19.361-15.064.211.027.42.064.639.064a5 5 0 0 0 5-5 5 5 0 0 0-5-5" fill="#44D860"/><path fill="#3ECC5F" d="M153 123h30v-20h-30z"/><path d="M193 115.5a2.5 2.5 0 1 0 0-5c-.109 0-.214.019-.319.032-.02-.075-.037-.15-.058-.225a2.501 2.501 0 0 0-.963-4.807c-.569 0-1.088.197-1.508.518a6.653 6.653 0 0 0-.168-.168c.314-.417.506-.931.506-1.494a2.5 2.5 0 0 0-4.8-.979A9.987 9.987 0 0 0 183 103c-5.522 0-10 4.478-10 10s4.478 10 10 10c.934 0 1.833-.138 2.69-.377a2.5 2.5 0 0 0 4.8-.979c0-.563-.192-1.077-.506-1.494.057-.055.113-.111.168-.168.42.321.939.518 1.508.518a2.5 2.5 0 0 0 .963-4.807c.021-.074.038-.15.058-.225.105.013.21.032.319.032" fill="#44D860"/><path d="M63 55.5a2.5 2.5 0 0 1-2.5-2.5c0-4.136-3.364-7.5-7.5-7.5s-7.5 3.364-7.5 7.5a2.5 2.5 0 1 1-5 0c0-6.893 5.607-12.5 12.5-12.5S65.5 46.107 65.5 53a2.5 2.5 0 0 1-2.5 2.5" fill="#000"/><path d="M103 183h60c11.046 0 20-8.954 20-20V93h-60c-11.046 0-20 8.954-20 20v70z" fill="#FFFF50"/><path d="M168.02 124h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 20h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 20h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0-49.814h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 19.814h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2m0 20h-50.04a1 1 0 1 1 0-2h50.04a1 1 0 1 1 0 2M183 61.611c-.012 0-.022-.006-.034-.005-3.09.105-4.552 3.196-5.842 5.923-1.346 2.85-2.387 4.703-4.093 4.647-1.889-.068-2.969-2.202-4.113-4.46-1.314-2.594-2.814-5.536-5.963-5.426-3.046.104-4.513 2.794-5.807 5.167-1.377 2.528-2.314 4.065-4.121 3.994-1.927-.07-2.951-1.805-4.136-3.813-1.321-2.236-2.848-4.75-5.936-4.664-2.994.103-4.465 2.385-5.763 4.4-1.373 2.13-2.335 3.428-4.165 3.351-1.973-.07-2.992-1.51-4.171-3.177-1.324-1.873-2.816-3.993-5.895-3.89-2.928.1-4.399 1.97-5.696 3.618-1.232 1.564-2.194 2.802-4.229 2.724a1 1 0 0 0-.072 2c3.017.101 4.545-1.8 5.872-3.487 1.177-1.496 2.193-2.787 4.193-2.855 1.926-.082 2.829 1.115 4.195 3.045 1.297 1.834 2.769 3.914 5.731 4.021 3.103.104 4.596-2.215 5.918-4.267 1.182-1.834 2.202-3.417 4.15-3.484 1.793-.067 2.769 1.35 4.145 3.681 1.297 2.197 2.766 4.686 5.787 4.796 3.125.108 4.634-2.62 5.949-5.035 1.139-2.088 2.214-4.06 4.119-4.126 1.793-.042 2.728 1.595 4.111 4.33 1.292 2.553 2.757 5.445 5.825 5.556l.169.003c3.064 0 4.518-3.075 5.805-5.794 1.139-2.41 2.217-4.68 4.067-4.773v-2z" fill="#000"/><path fill="#3ECC5F" d="M83 183h40v-40H83z"/><path d="M143 158c-.219 0-.428.037-.639.064-.038-.15-.074-.301-.116-.451A5 5 0 0 0 140.32 148a4.96 4.96 0 0 0-3.016 1.036 26.531 26.531 0 0 0-.335-.336 4.955 4.955 0 0 0 1.011-2.987 5 5 0 0 0-9.599-1.959c-.148-.042-.297-.077-.445-.115.027-.211.064-.42.064-.639a5 5 0 0 0-5-5 5 5 0 0 0-5 5c0 .219.037.428.064.639-.148.038-.297.073-.445.115a4.998 4.998 0 0 0-9.599 1.959c0 1.125.384 2.151 1.011 2.987-3.717 3.632-6.031 8.693-6.031 14.3 0 11.046 8.954 20 20 20 9.339 0 17.16-6.41 19.361-15.064.211.027.42.064.639.064a5 5 0 0 0 5-5 5 5 0 0 0-5-5" fill="#44D860"/><path fill="#3ECC5F" d="M83 123h40v-20H83z"/><path d="M133 115.5a2.5 2.5 0 1 0 0-5c-.109 0-.214.019-.319.032-.02-.075-.037-.15-.058-.225a2.501 2.501 0 0 0-.963-4.807c-.569 0-1.088.197-1.508.518a6.653 6.653 0 0 0-.168-.168c.314-.417.506-.931.506-1.494a2.5 2.5 0 0 0-4.8-.979A9.987 9.987 0 0 0 123 103c-5.522 0-10 4.478-10 10s4.478 10 10 10c.934 0 1.833-.138 2.69-.377a2.5 2.5 0 0 0 4.8-.979c0-.563-.192-1.077-.506-1.494.057-.055.113-.111.168-.168.42.321.939.518 1.508.518a2.5 2.5 0 0 0 .963-4.807c.021-.074.038-.15.058-.225.105.013.21.032.319.032" fill="#44D860"/><path d="M143 41.75c-.16 0-.33-.02-.49-.05a2.52 2.52 0 0 1-.47-.14c-.15-.06-.29-.14-.431-.23-.13-.09-.259-.2-.38-.31-.109-.12-.219-.24-.309-.38s-.17-.28-.231-.43a2.619 2.619 0 0 1-.189-.96c0-.16.02-.33.05-.49.03-.16.08-.31.139-.47.061-.15.141-.29.231-.43.09-.13.2-.26.309-.38.121-.11.25-.22.38-.31.141-.09.281-.17.431-.23.149-.06.31-.11.47-.14.32-.07.65-.07.98 0 .159.03.32.08.47.14.149.06.29.14.43.23.13.09.259.2.38.31.11.12.22.25.31.38.09.14.17.28.23.43.06.16.11.31.14.47.029.16.05.33.05.49 0 .66-.271 1.31-.73 1.77-.121.11-.25.22-.38.31-.14.09-.281.17-.43.23a2.565 2.565 0 0 1-.96.19m20-1.25c-.66 0-1.3-.27-1.771-.73a3.802 3.802 0 0 1-.309-.38c-.09-.14-.17-.28-.231-.43a2.619 2.619 0 0 1-.189-.96c0-.66.27-1.3.729-1.77.121-.11.25-.22.38-.31.141-.09.281-.17.431-.23.149-.06.31-.11.47-.14.32-.07.66-.07.98 0 .159.03.32.08.47.14.149.06.29.14.43.23.13.09.259.2.38.31.459.47.73 1.11.73 1.77 0 .16-.021.33-.05.49-.03.16-.08.32-.14.47-.07.15-.14.29-.23.43-.09.13-.2.26-.31.38-.121.11-.25.22-.38.31-.14.09-.281.17-.43.23a2.565 2.565 0 0 1-.96.19" fill="#000"/></g></svg> \ No newline at end of file
diff --git a/docs/static/img/studip-hilfe.png b/docs/static/img/studip-hilfe.png
new file mode 100644
index 0000000..a14a0dd
--- /dev/null
+++ b/docs/static/img/studip-hilfe.png
Binary files differ
diff --git a/docs/static/img/undraw_docusaurus_mountain.svg b/docs/static/img/undraw_docusaurus_mountain.svg
new file mode 100644
index 0000000..431cef2
--- /dev/null
+++ b/docs/static/img/undraw_docusaurus_mountain.svg
@@ -0,0 +1,170 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1088" height="687.962" viewBox="0 0 1088 687.962">
+ <g id="Group_12" data-name="Group 12" transform="translate(-57 -56)">
+ <g id="Group_11" data-name="Group 11" transform="translate(57 56)">
+ <path id="Path_83" data-name="Path 83" d="M1017.81,560.461c-5.27,45.15-16.22,81.4-31.25,110.31-20,38.52-54.21,54.04-84.77,70.28a193.275,193.275,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.282,657.282,0,0,0-104.09-13.16q-14.97-.675-29.97-.67c-15.42.02-293.07,5.29-360.67-131.57-16.69-33.76-28.13-75-32.24-125.27-11.63-142.12,52.29-235.46,134.74-296.47,155.97-115.41,369.76-110.57,523.43,7.88C941.15,276.621,1036.99,396.031,1017.81,560.461Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
+ <path id="Path_84" data-name="Path 84" d="M986.56,670.771c-20,38.52-47.21,64.04-77.77,80.28a193.272,193.272,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.3,657.3,0,0,0-104.09-13.16q-14.97-.675-29.97-.67-23.13.03-46.25,1.72c-100.17,7.36-253.82-6.43-321.42-143.29L382,283.981,444.95,445.6l20.09,51.59,55.37-75.98L549,381.981l130.2,149.27,36.8-81.27L970.78,657.9l14.21,11.59Z" transform="translate(-56 -106.019)" fill="#f2f2f2"/>
+ <path id="Path_85" data-name="Path 85" d="M302,282.962l26-57,36,83-31-60Z" opacity="0.1"/>
+ <path id="Path_86" data-name="Path 86" d="M610.5,753.821q-14.97-.675-29.97-.67L465.04,497.191Z" transform="translate(-56 -106.019)" opacity="0.1"/>
+ <path id="Path_87" data-name="Path 87" d="M464.411,315.191,493,292.962l130,150-132-128Z" opacity="0.1"/>
+ <path id="Path_88" data-name="Path 88" d="M908.79,751.051a193.265,193.265,0,0,1-27.46,11.94L679.2,531.251Z" transform="translate(-56 -106.019)" opacity="0.1"/>
+ <circle id="Ellipse_11" data-name="Ellipse 11" cx="3" cy="3" r="3" transform="translate(479 98.962)" fill="#f2f2f2"/>
+ <circle id="Ellipse_12" data-name="Ellipse 12" cx="3" cy="3" r="3" transform="translate(396 201.962)" fill="#f2f2f2"/>
+ <circle id="Ellipse_13" data-name="Ellipse 13" cx="2" cy="2" r="2" transform="translate(600 220.962)" fill="#f2f2f2"/>
+ <circle id="Ellipse_14" data-name="Ellipse 14" cx="2" cy="2" r="2" transform="translate(180 265.962)" fill="#f2f2f2"/>
+ <circle id="Ellipse_15" data-name="Ellipse 15" cx="2" cy="2" r="2" transform="translate(612 96.962)" fill="#f2f2f2"/>
+ <circle id="Ellipse_16" data-name="Ellipse 16" cx="2" cy="2" r="2" transform="translate(736 192.962)" fill="#f2f2f2"/>
+ <circle id="Ellipse_17" data-name="Ellipse 17" cx="2" cy="2" r="2" transform="translate(858 344.962)" fill="#f2f2f2"/>
+ <path id="Path_89" data-name="Path 89" d="M306,121.222h-2.76v-2.76h-1.48v2.76H299V122.7h2.76v2.759h1.48V122.7H306Z" fill="#f2f2f2"/>
+ <path id="Path_90" data-name="Path 90" d="M848,424.222h-2.76v-2.76h-1.48v2.76H841V425.7h2.76v2.759h1.48V425.7H848Z" fill="#f2f2f2"/>
+ <path id="Path_91" data-name="Path 91" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
+ <path id="Path_92" data-name="Path 92" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" opacity="0.1"/>
+ <ellipse id="Ellipse_18" data-name="Ellipse 18" cx="544" cy="30" rx="544" ry="30" transform="translate(0 583.962)" fill="#3f3d56"/>
+ <path id="Path_93" data-name="Path 93" d="M624,677.981c0,33.137-14.775,24-33,24s-33,9.137-33-24,33-96,33-96S624,644.844,624,677.981Z" transform="translate(-56 -106.019)" fill="#ff6584"/>
+ <path id="Path_94" data-name="Path 94" d="M606,690.66c0,15.062-6.716,10.909-15,10.909s-15,4.153-15-10.909,15-43.636,15-43.636S606,675.6,606,690.66Z" transform="translate(-56 -106.019)" opacity="0.1"/>
+ <rect id="Rectangle_97" data-name="Rectangle 97" width="92" height="18" rx="9" transform="translate(489 604.962)" fill="#2f2e41"/>
+ <rect id="Rectangle_98" data-name="Rectangle 98" width="92" height="18" rx="9" transform="translate(489 586.962)" fill="#2f2e41"/>
+ <path id="Path_95" data-name="Path 95" d="M193,596.547c0,55.343,34.719,100.126,77.626,100.126" transform="translate(-56 -106.019)" fill="#3f3d56"/>
+ <path id="Path_96" data-name="Path 96" d="M270.626,696.673c0-55.965,38.745-101.251,86.626-101.251" transform="translate(-56 -106.019)" fill="#6c63ff"/>
+ <path id="Path_97" data-name="Path 97" d="M221.125,601.564c0,52.57,22.14,95.109,49.5,95.109" transform="translate(-56 -106.019)" fill="#6c63ff"/>
+ <path id="Path_98" data-name="Path 98" d="M270.626,696.673c0-71.511,44.783-129.377,100.126-129.377" transform="translate(-56 -106.019)" fill="#3f3d56"/>
+ <path id="Path_99" data-name="Path 99" d="M254.3,697.379s11.009-.339,14.326-2.7,16.934-5.183,17.757-1.395,16.544,18.844,4.115,18.945-28.879-1.936-32.19-3.953S254.3,697.379,254.3,697.379Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
+ <path id="Path_100" data-name="Path 100" d="M290.716,710.909c-12.429.1-28.879-1.936-32.19-3.953-2.522-1.536-3.527-7.048-3.863-9.591l-.368.014s.7,8.879,4.009,10.9,19.761,4.053,32.19,3.953c3.588-.029,4.827-1.305,4.759-3.2C294.755,710.174,293.386,710.887,290.716,710.909Z" transform="translate(-56 -106.019)" opacity="0.2"/>
+ <path id="Path_101" data-name="Path 101" d="M777.429,633.081c0,38.029,23.857,68.8,53.341,68.8" transform="translate(-56 -106.019)" fill="#3f3d56"/>
+ <path id="Path_102" data-name="Path 102" d="M830.769,701.882c0-38.456,26.623-69.575,59.525-69.575" transform="translate(-56 -106.019)" fill="#6c63ff"/>
+ <path id="Path_103" data-name="Path 103" d="M796.755,636.528c0,36.124,15.213,65.354,34.014,65.354" transform="translate(-56 -106.019)" fill="#6c63ff"/>
+ <path id="Path_104" data-name="Path 104" d="M830.769,701.882c0-49.139,30.773-88.9,68.8-88.9" transform="translate(-56 -106.019)" fill="#3f3d56"/>
+ <path id="Path_105" data-name="Path 105" d="M819.548,702.367s7.565-.233,9.844-1.856,11.636-3.562,12.2-.958,11.368,12.949,2.828,13.018-19.844-1.33-22.119-2.716S819.548,702.367,819.548,702.367Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
+ <path id="Path_106" data-name="Path 106" d="M844.574,711.664c-8.54.069-19.844-1.33-22.119-2.716-1.733-1.056-2.423-4.843-2.654-6.59l-.253.01s.479,6.1,2.755,7.487,13.579,2.785,22.119,2.716c2.465-.02,3.317-.9,3.27-2.2C847.349,711.159,846.409,711.649,844.574,711.664Z" transform="translate(-56 -106.019)" opacity="0.2"/>
+ <path id="Path_107" data-name="Path 107" d="M949.813,724.718s11.36-1.729,14.5-4.591,16.89-7.488,18.217-3.667,19.494,17.447,6.633,19.107-30.153,1.609-33.835-.065S949.813,724.718,949.813,724.718Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
+ <path id="Path_108" data-name="Path 108" d="M989.228,734.173c-12.86,1.659-30.153,1.609-33.835-.065-2.8-1.275-4.535-6.858-5.2-9.45l-.379.061s1.833,9.109,5.516,10.783,20.975,1.725,33.835.065c3.712-.479,4.836-1.956,4.529-3.906C993.319,732.907,991.991,733.817,989.228,734.173Z" transform="translate(-56 -106.019)" opacity="0.2"/>
+ <path id="Path_109" data-name="Path 109" d="M670.26,723.9s9.587-1.459,12.237-3.875,14.255-6.32,15.374-3.095,16.452,14.725,5.6,16.125-25.448,1.358-28.555-.055S670.26,723.9,670.26,723.9Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
+ <path id="Path_110" data-name="Path 110" d="M703.524,731.875c-10.853,1.4-25.448,1.358-28.555-.055-2.367-1.076-3.827-5.788-4.39-7.976l-.32.051s1.547,7.687,4.655,9.1,17.7,1.456,28.555.055c3.133-.4,4.081-1.651,3.822-3.3C706.977,730.807,705.856,731.575,703.524,731.875Z" transform="translate(-56 -106.019)" opacity="0.2"/>
+ <path id="Path_111" data-name="Path 111" d="M178.389,719.109s7.463-1.136,9.527-3.016,11.1-4.92,11.969-2.409,12.808,11.463,4.358,12.553-19.811,1.057-22.23-.043S178.389,719.109,178.389,719.109Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
+ <path id="Path_112" data-name="Path 112" d="M204.285,725.321c-8.449,1.09-19.811,1.057-22.23-.043-1.842-.838-2.979-4.506-3.417-6.209l-.249.04s1.2,5.984,3.624,7.085,13.781,1.133,22.23.043c2.439-.315,3.177-1.285,2.976-2.566C206.973,724.489,206.1,725.087,204.285,725.321Z" transform="translate(-56 -106.019)" opacity="0.2"/>
+ <path id="Path_113" data-name="Path 113" d="M439.7,707.337c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873,42.118-36.793,93.694-36.793S439.7,677.117,439.7,707.337Z" transform="translate(-56 -106.019)" opacity="0.1"/>
+ <path id="Path_114" data-name="Path 114" d="M439.7,699.9c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873S295.04,663.1,346.616,663.1,439.7,669.676,439.7,699.9Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
+ </g>
+ <g id="docusaurus_keytar" transform="translate(312.271 493.733)">
+ <path id="Path_40" data-name="Path 40" d="M99,52h91.791V89.153H99Z" transform="translate(5.904 -14.001)" fill="#fff" fill-rule="evenodd"/>
+ <path id="Path_41" data-name="Path 41" d="M24.855,163.927A21.828,21.828,0,0,1,5.947,153a21.829,21.829,0,0,0,18.908,32.782H46.71V163.927Z" transform="translate(-3 -4.634)" fill="#3ecc5f" fill-rule="evenodd"/>
+ <path id="Path_42" data-name="Path 42" d="M121.861,61.1l76.514-4.782V45.39A21.854,21.854,0,0,0,176.52,23.535H78.173L75.441,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L64.513,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L53.586,18.8a3.154,3.154,0,0,0-5.464,0L45.39,23.535c-.024,0-.046,0-.071,0l-4.526-4.525a3.153,3.153,0,0,0-5.276,1.414l-1.5,5.577-5.674-1.521a3.154,3.154,0,0,0-3.863,3.864L26,34.023l-5.575,1.494a3.155,3.155,0,0,0-1.416,5.278l4.526,4.526c0,.023,0,.046,0,.07L18.8,48.122a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,59.05a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,69.977a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,80.9a3.154,3.154,0,0,0,0,5.464L23.535,89.1,18.8,91.832a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,102.76a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,113.687a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,124.615a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,135.542a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,146.469a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,157.4a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,168.324a3.154,3.154,0,0,0,0,5.464l4.732,2.732A21.854,21.854,0,0,0,45.39,198.375H176.52a21.854,21.854,0,0,0,21.855-21.855V89.1l-76.514-4.782a11.632,11.632,0,0,1,0-23.219" transform="translate(-1.681 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
+ <path id="Path_43" data-name="Path 43" d="M143,186.71h32.782V143H143Z" transform="translate(9.984 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
+ <path id="Path_44" data-name="Path 44" d="M196.71,159.855a5.438,5.438,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(10.912 -6.025)" fill="#44d860" fill-rule="evenodd"/>
+ <path id="Path_45" data-name="Path 45" d="M153,124.855h32.782V103H153Z" transform="translate(10.912 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
+ <path id="Path_46" data-name="Path 46" d="M194.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.814,2.814,0,0,0,.349.035" transform="translate(12.767 -9.377)" fill="#44d860" fill-rule="evenodd"/>
+ <path id="Path_47" data-name="Path 47" d="M65.087,56.891a2.732,2.732,0,0,1-2.732-2.732,8.2,8.2,0,0,0-16.391,0,2.732,2.732,0,0,1-5.464,0,13.659,13.659,0,0,1,27.319,0,2.732,2.732,0,0,1-2.732,2.732" transform="translate(0.478 -15.068)" fill-rule="evenodd"/>
+ <path id="Path_48" data-name="Path 48" d="M103,191.347h65.565a21.854,21.854,0,0,0,21.855-21.855V93H124.855A21.854,21.854,0,0,0,103,114.855Z" transform="translate(6.275 -10.199)" fill="#ffff50" fill-rule="evenodd"/>
+ <path id="Path_49" data-name="Path 49" d="M173.216,129.787H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0-54.434H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.652H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186M189.585,61.611c-.013,0-.024-.007-.037-.005-3.377.115-4.974,3.492-6.384,6.472-1.471,3.114-2.608,5.139-4.473,5.078-2.064-.074-3.244-2.406-4.494-4.874-1.436-2.835-3.075-6.049-6.516-5.929-3.329.114-4.932,3.053-6.346,5.646-1.5,2.762-2.529,4.442-4.5,4.364-2.106-.076-3.225-1.972-4.52-4.167-1.444-2.443-3.112-5.191-6.487-5.1-3.272.113-4.879,2.606-6.3,4.808-1.5,2.328-2.552,3.746-4.551,3.662-2.156-.076-3.27-1.65-4.558-3.472-1.447-2.047-3.077-4.363-6.442-4.251-3.2.109-4.807,2.153-6.224,3.954-1.346,1.709-2.4,3.062-4.621,2.977a1.093,1.093,0,0,0-.079,2.186c3.3.11,4.967-1.967,6.417-3.81,1.286-1.635,2.4-3.045,4.582-3.12,2.1-.09,3.091,1.218,4.584,3.327,1.417,2,3.026,4.277,6.263,4.394,3.391.114,5.022-2.42,6.467-4.663,1.292-2,2.406-3.734,4.535-3.807,1.959-.073,3.026,1.475,4.529,4.022,1.417,2.4,3.023,5.121,6.324,5.241,3.415.118,5.064-2.863,6.5-5.5,1.245-2.282,2.419-4.437,4.5-4.509,1.959-.046,2.981,1.743,4.492,4.732,1.412,2.79,3.013,5.95,6.365,6.071l.185,0c3.348,0,4.937-3.36,6.343-6.331,1.245-2.634,2.423-5.114,4.444-5.216Z" transform="translate(7.109 -13.11)" fill-rule="evenodd"/>
+ <path id="Path_50" data-name="Path 50" d="M83,186.71h43.71V143H83Z" transform="translate(4.42 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
+ <g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 109.327, 91.085)">
+ <rect id="Rectangle_3" data-name="Rectangle 3" width="92.361" height="36.462" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
+ <g id="Group_2" data-name="Group 2" transform="translate(1.531 23.03)">
+ <rect id="Rectangle_4" data-name="Rectangle 4" width="5.336" height="5.336" rx="1" transform="translate(16.797 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_5" data-name="Rectangle 5" width="5.336" height="5.336" rx="1" transform="translate(23.12 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_6" data-name="Rectangle 6" width="5.336" height="5.336" rx="1" transform="translate(29.444 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_7" data-name="Rectangle 7" width="5.336" height="5.336" rx="1" transform="translate(35.768 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_8" data-name="Rectangle 8" width="5.336" height="5.336" rx="1" transform="translate(42.091 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_9" data-name="Rectangle 9" width="5.336" height="5.336" rx="1" transform="translate(48.415 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_10" data-name="Rectangle 10" width="5.336" height="5.336" rx="1" transform="translate(54.739 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_11" data-name="Rectangle 11" width="5.336" height="5.336" rx="1" transform="translate(61.063 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_12" data-name="Rectangle 12" width="5.336" height="5.336" rx="1" transform="translate(67.386 0)" fill="#4a4a4a"/>
+ <path id="Path_51" data-name="Path 51" d="M1.093,0H14.518a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0ZM75,0H88.426a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H75a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,75,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
+ </g>
+ <g id="Group_3" data-name="Group 3" transform="translate(1.531 10.261)">
+ <path id="Path_52" data-name="Path 52" d="M1.093,0H6.218A1.093,1.093,0,0,1,7.31,1.093V4.242A1.093,1.093,0,0,1,6.218,5.335H1.093A1.093,1.093,0,0,1,0,4.242V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
+ <rect id="Rectangle_13" data-name="Rectangle 13" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_14" data-name="Rectangle 14" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_15" data-name="Rectangle 15" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_16" data-name="Rectangle 16" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_17" data-name="Rectangle 17" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_18" data-name="Rectangle 18" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_19" data-name="Rectangle 19" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_20" data-name="Rectangle 20" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_21" data-name="Rectangle 21" width="5.336" height="5.336" rx="1" transform="translate(58.888 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_22" data-name="Rectangle 22" width="5.336" height="5.336" rx="1" transform="translate(65.212 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_23" data-name="Rectangle 23" width="5.336" height="5.336" rx="1" transform="translate(71.536 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_24" data-name="Rectangle 24" width="5.336" height="5.336" rx="1" transform="translate(77.859 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_25" data-name="Rectangle 25" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
+ </g>
+ <g id="Group_4" data-name="Group 4" transform="translate(91.05 9.546) rotate(180)">
+ <path id="Path_53" data-name="Path 53" d="M1.093,0H6.219A1.093,1.093,0,0,1,7.312,1.093v3.15A1.093,1.093,0,0,1,6.219,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
+ <rect id="Rectangle_26" data-name="Rectangle 26" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_27" data-name="Rectangle 27" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_28" data-name="Rectangle 28" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_29" data-name="Rectangle 29" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_30" data-name="Rectangle 30" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_31" data-name="Rectangle 31" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_32" data-name="Rectangle 32" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_33" data-name="Rectangle 33" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_34" data-name="Rectangle 34" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_35" data-name="Rectangle 35" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_36" data-name="Rectangle 36" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_37" data-name="Rectangle 37" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_38" data-name="Rectangle 38" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_39" data-name="Rectangle 39" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_40" data-name="Rectangle 40" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_41" data-name="Rectangle 41" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_42" data-name="Rectangle 42" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_43" data-name="Rectangle 43" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_44" data-name="Rectangle 44" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_45" data-name="Rectangle 45" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_46" data-name="Rectangle 46" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_47" data-name="Rectangle 47" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_48" data-name="Rectangle 48" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_49" data-name="Rectangle 49" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_50" data-name="Rectangle 50" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_51" data-name="Rectangle 51" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
+ </g>
+ <g id="Group_6" data-name="Group 6" transform="translate(1.531 16.584)">
+ <path id="Path_54" data-name="Path 54" d="M1.093,0h7.3A1.093,1.093,0,0,1,9.485,1.093v3.15A1.093,1.093,0,0,1,8.392,5.336h-7.3A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
+ <g id="Group_5" data-name="Group 5" transform="translate(10.671 0)">
+ <rect id="Rectangle_52" data-name="Rectangle 52" width="5.336" height="5.336" rx="1" fill="#4a4a4a"/>
+ <rect id="Rectangle_53" data-name="Rectangle 53" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_54" data-name="Rectangle 54" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_55" data-name="Rectangle 55" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_56" data-name="Rectangle 56" width="5.336" height="5.336" rx="1" transform="translate(25.295 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_57" data-name="Rectangle 57" width="5.336" height="5.336" rx="1" transform="translate(31.619 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_58" data-name="Rectangle 58" width="5.336" height="5.336" rx="1" transform="translate(37.942 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_59" data-name="Rectangle 59" width="5.336" height="5.336" rx="1" transform="translate(44.265 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_60" data-name="Rectangle 60" width="5.336" height="5.336" rx="1" transform="translate(50.589 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_61" data-name="Rectangle 61" width="5.336" height="5.336" rx="1" transform="translate(56.912 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_62" data-name="Rectangle 62" width="5.336" height="5.336" rx="1" transform="translate(63.236 0)" fill="#4a4a4a"/>
+ </g>
+ <path id="Path_55" data-name="Path 55" d="M1.094,0H8A1.093,1.093,0,0,1,9.091,1.093v3.15A1.093,1.093,0,0,1,8,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(80.428 0)" fill="#4a4a4a" fill-rule="evenodd"/>
+ </g>
+ <g id="Group_7" data-name="Group 7" transform="translate(1.531 29.627)">
+ <rect id="Rectangle_63" data-name="Rectangle 63" width="5.336" height="5.336" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_64" data-name="Rectangle 64" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_65" data-name="Rectangle 65" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_66" data-name="Rectangle 66" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
+ <path id="Path_56" data-name="Path 56" d="M1.093,0H31.515a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.244V1.093A1.093,1.093,0,0,1,1.093,0ZM34.687,0h3.942a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H34.687a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,34.687,0Z" transform="translate(25.294 0)" fill="#4a4a4a" fill-rule="evenodd"/>
+ <rect id="Rectangle_67" data-name="Rectangle 67" width="5.336" height="5.336" rx="1" transform="translate(66.003 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_68" data-name="Rectangle 68" width="5.336" height="5.336" rx="1" transform="translate(72.327 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_69" data-name="Rectangle 69" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
+ <path id="Path_57" data-name="Path 57" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(83.59 2.273) rotate(180)" fill="#4a4a4a"/>
+ <path id="Path_58" data-name="Path 58" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(78.255 3.063)" fill="#4a4a4a"/>
+ </g>
+ <rect id="Rectangle_70" data-name="Rectangle 70" width="88.927" height="2.371" rx="1.085" transform="translate(1.925 1.17)" fill="#4a4a4a"/>
+ <rect id="Rectangle_71" data-name="Rectangle 71" width="4.986" height="1.581" rx="0.723" transform="translate(4.1 1.566)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_72" data-name="Rectangle 72" width="4.986" height="1.581" rx="0.723" transform="translate(10.923 1.566)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_73" data-name="Rectangle 73" width="4.986" height="1.581" rx="0.723" transform="translate(16.173 1.566)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_74" data-name="Rectangle 74" width="4.986" height="1.581" rx="0.723" transform="translate(21.421 1.566)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_75" data-name="Rectangle 75" width="4.986" height="1.581" rx="0.723" transform="translate(26.671 1.566)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_76" data-name="Rectangle 76" width="4.986" height="1.581" rx="0.723" transform="translate(33.232 1.566)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_77" data-name="Rectangle 77" width="4.986" height="1.581" rx="0.723" transform="translate(38.48 1.566)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_78" data-name="Rectangle 78" width="4.986" height="1.581" rx="0.723" transform="translate(43.73 1.566)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_79" data-name="Rectangle 79" width="4.986" height="1.581" rx="0.723" transform="translate(48.978 1.566)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_80" data-name="Rectangle 80" width="4.986" height="1.581" rx="0.723" transform="translate(55.54 1.566)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_81" data-name="Rectangle 81" width="4.986" height="1.581" rx="0.723" transform="translate(60.788 1.566)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_82" data-name="Rectangle 82" width="4.986" height="1.581" rx="0.723" transform="translate(66.038 1.566)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_83" data-name="Rectangle 83" width="4.986" height="1.581" rx="0.723" transform="translate(72.599 1.566)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_84" data-name="Rectangle 84" width="4.986" height="1.581" rx="0.723" transform="translate(77.847 1.566)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_85" data-name="Rectangle 85" width="4.986" height="1.581" rx="0.723" transform="translate(83.097 1.566)" fill="#d8d8d8" opacity="0.136"/>
+ </g>
+ <path id="Path_59" data-name="Path 59" d="M146.71,159.855a5.439,5.439,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(6.275 -6.025)" fill="#44d860" fill-rule="evenodd"/>
+ <path id="Path_60" data-name="Path 60" d="M83,124.855h43.71V103H83Z" transform="translate(4.42 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
+ <path id="Path_61" data-name="Path 61" d="M134.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.811,2.811,0,0,0,.349.035" transform="translate(7.202 -9.377)" fill="#44d860" fill-rule="evenodd"/>
+ <path id="Path_62" data-name="Path 62" d="M143.232,42.33a2.967,2.967,0,0,1-.535-.055,2.754,2.754,0,0,1-.514-.153,2.838,2.838,0,0,1-.471-.251,4.139,4.139,0,0,1-.415-.339,3.2,3.2,0,0,1-.338-.415A2.7,2.7,0,0,1,140.5,39.6a2.968,2.968,0,0,1,.055-.535,3.152,3.152,0,0,1,.152-.514,2.874,2.874,0,0,1,.252-.47,2.633,2.633,0,0,1,.753-.754,2.837,2.837,0,0,1,.471-.251,2.753,2.753,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,4.019,4.019,0,0,1,.339.415,2.786,2.786,0,0,1,.251.47,2.864,2.864,0,0,1,.208,1.049,2.77,2.77,0,0,1-.8,1.934,4.139,4.139,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459m21.855-1.366a2.789,2.789,0,0,1-1.935-.8,4.162,4.162,0,0,1-.338-.415,2.7,2.7,0,0,1-.459-1.519,2.789,2.789,0,0,1,.8-1.934,4.139,4.139,0,0,1,.415-.339,2.838,2.838,0,0,1,.471-.251,2.752,2.752,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,2.79,2.79,0,0,1,.8,1.934,3.069,3.069,0,0,1-.055.535,2.779,2.779,0,0,1-.153.514,3.885,3.885,0,0,1-.251.47,4.02,4.02,0,0,1-.339.415,4.138,4.138,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459" transform="translate(9.753 -15.532)" fill-rule="evenodd"/>
+ </g>
+ </g>
+</svg>
diff --git a/docs/static/img/undraw_docusaurus_react.svg b/docs/static/img/undraw_docusaurus_react.svg
new file mode 100644
index 0000000..e417050
--- /dev/null
+++ b/docs/static/img/undraw_docusaurus_react.svg
@@ -0,0 +1,169 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1041.277" height="554.141" viewBox="0 0 1041.277 554.141">
+ <g id="Group_24" data-name="Group 24" transform="translate(-440 -263)">
+ <g id="Group_23" data-name="Group 23" transform="translate(439.989 262.965)">
+ <path id="Path_299" data-name="Path 299" d="M1040.82,611.12q-1.74,3.75-3.47,7.4-2.7,5.67-5.33,11.12c-.78,1.61-1.56,3.19-2.32,4.77-8.6,17.57-16.63,33.11-23.45,45.89A73.21,73.21,0,0,1,942.44,719l-151.65,1.65h-1.6l-13,.14-11.12.12-34.1.37h-1.38l-17.36.19h-.53l-107,1.16-95.51,1-11.11.12-69,.75H429l-44.75.48h-.48l-141.5,1.53-42.33.46a87.991,87.991,0,0,1-10.79-.54h0c-1.22-.14-2.44-.3-3.65-.49a87.38,87.38,0,0,1-51.29-27.54C116,678.37,102.75,655,93.85,629.64q-1.93-5.49-3.6-11.12C59.44,514.37,97,380,164.6,290.08q4.25-5.64,8.64-11l.07-.08c20.79-25.52,44.1-46.84,68.93-62,44-26.91,92.75-34.49,140.7-11.9,40.57,19.12,78.45,28.11,115.17,30.55,3.71.24,7.42.42,11.11.53,84.23,2.65,163.17-27.7,255.87-47.29,3.69-.78,7.39-1.55,11.12-2.28,66.13-13.16,139.49-20.1,226.73-5.51a189.089,189.089,0,0,1,26.76,6.4q5.77,1.86,11.12,4c41.64,16.94,64.35,48.24,74,87.46q1.37,5.46,2.37,11.11C1134.3,384.41,1084.19,518.23,1040.82,611.12Z" transform="translate(-79.34 -172.91)" fill="#f2f2f2"/>
+ <path id="Path_300" data-name="Path 300" d="M576.36,618.52a95.21,95.21,0,0,1-1.87,11.12h93.7V618.52Zm-78.25,62.81,11.11-.09V653.77c-3.81-.17-7.52-.34-11.11-.52ZM265.19,618.52v11.12h198.5V618.52ZM1114.87,279h-74V191.51q-5.35-2.17-11.12-4V279H776.21V186.58c-3.73.73-7.43,1.5-11.12,2.28V279H509.22V236.15c-3.69-.11-7.4-.29-11.11-.53V279H242.24V217c-24.83,15.16-48.14,36.48-68.93,62h-.07v.08q-4.4,5.4-8.64,11h8.64V618.52h-83q1.66,5.63,3.6,11.12h79.39v93.62a87,87,0,0,0,12.2,2.79c1.21.19,2.43.35,3.65.49h0a87.991,87.991,0,0,0,10.79.54l42.33-.46v-97H498.11v94.21l11.11-.12V629.64H765.09V721l11.12-.12V629.64H1029.7v4.77c.76-1.58,1.54-3.16,2.32-4.77q2.63-5.45,5.33-11.12,1.73-3.64,3.47-7.4v-321h76.42Q1116.23,284.43,1114.87,279ZM242.24,618.52V290.08H498.11V618.52Zm267,0V290.08H765.09V618.52Zm520.48,0H776.21V290.08H1029.7Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
+ <path id="Path_301" data-name="Path 301" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" fill="#65617d"/>
+ <path id="Path_302" data-name="Path 302" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" opacity="0.2"/>
+ <path id="Path_303" data-name="Path 303" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
+ <path id="Path_304" data-name="Path 304" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
+ <path id="Path_305" data-name="Path 305" d="M377.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
+ <rect id="Rectangle_137" data-name="Rectangle 137" width="47.17" height="31.5" transform="translate(680.92 483.65)" fill="#3f3d56"/>
+ <rect id="Rectangle_138" data-name="Rectangle 138" width="47.17" height="31.5" transform="translate(680.92 483.65)" opacity="0.1"/>
+ <rect id="Rectangle_139" data-name="Rectangle 139" width="47.17" height="31.5" transform="translate(678.92 483.65)" fill="#3f3d56"/>
+ <path id="Path_306" data-name="Path 306" d="M298.09,483.65v4.97l-47.17,1.26v-6.23Z" opacity="0.1"/>
+ <path id="Path_307" data-name="Path 307" d="M460.69,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6a4,4,0,0,1,3.95,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
+ <path id="Path_308" data-name="Path 308" d="M265.19,481.32v181.2h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
+ <path id="Path_309" data-name="Path 309" d="M194.59,319.15h177.5V467.4l-177.5,4Z" fill="#39374d"/>
+ <path id="Path_310" data-name="Path 310" d="M726.09,483.65v6.41l-47.17-1.26v-5.15Z" opacity="0.1"/>
+ <path id="Path_311" data-name="Path 311" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0L672,657.42a4,4,0,0,1-3.85-3.95V485.27a4,4,0,0,1,3.95-3.95H863.7a4,4,0,0,1,3.99,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
+ <path id="Path_312" data-name="Path 312" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0V481.32h0a4,4,0,0,1,4,3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
+ <path id="Path_313" data-name="Path 313" d="M775.59,319.15H598.09V467.4l177.5,4Z" fill="#39374d"/>
+ <path id="Path_314" data-name="Path 314" d="M663.19,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h0a4,4,0,0,1-4-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6A4,4,0,0,1,663.19,485.27Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
+ <path id="Path_315" data-name="Path 315" d="M397.09,319.15h177.5V467.4l-177.5,4Z" fill="#4267b2"/>
+ <path id="Path_316" data-name="Path 316" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l202.51-1.33h.48l40.99-.28h.19l283.08-1.87h.29l.17-.01h.47l4.79-.03h1.46l74.49-.5,4.4-.02.98-.01Z" opacity="0.1"/>
+ <circle id="Ellipse_111" data-name="Ellipse 111" cx="51.33" cy="51.33" r="51.33" transform="translate(435.93 246.82)" fill="#fbbebe"/>
+ <path id="Path_317" data-name="Path 317" d="M617.94,550.07s-99.5,12-90,0c3.44-4.34,4.39-17.2,4.2-31.85-.06-4.45-.22-9.06-.45-13.65-1.1-22-3.75-43.5-3.75-43.5s87-41,77-8.5c-4,13.13-2.69,31.57.35,48.88.89,5.05,1.92,10,3,14.7a344.66,344.66,0,0,0,9.65,33.92Z" transform="translate(-79.34 -172.91)" fill="#fbbebe"/>
+ <path id="Path_318" data-name="Path 318" d="M585.47,546c11.51-2.13,23.7-6,34.53-1.54,2.85,1.17,5.47,2.88,8.39,3.86s6.12,1.22,9.16,1.91c10.68,2.42,19.34,10.55,24.9,20s8.44,20.14,11.26,30.72l6.9,25.83c6,22.45,12,45.09,13.39,68.3a2437.506,2437.506,0,0,1-250.84,1.43c5.44-10.34,11-21.31,10.54-33s-7.19-23.22-4.76-34.74c1.55-7.34,6.57-13.39,9.64-20.22,8.75-19.52,1.94-45.79,17.32-60.65,6.92-6.68,17-9.21,26.63-8.89,12.28.41,24.85,4.24,37,6.11C555.09,547.48,569.79,548.88,585.47,546Z" transform="translate(-79.34 -172.91)" fill="#ff6584"/>
+ <path id="Path_319" data-name="Path 319" d="M716.37,657.17l-.1,1.43v.1l-.17,2.3-1.33,18.51-1.61,22.3-.46,6.28-1,13.44v.17l-107,1-175.59,1.9v.84h-.14v-1.12l.45-14.36.86-28.06.74-23.79.07-2.37a10.53,10.53,0,0,1,11.42-10.17c4.72.4,10.85.89,18.18,1.41l3,.22c42.33,2.94,120.56,6.74,199.5,2,1.66-.09,3.33-.19,5-.31,12.24-.77,24.47-1.76,36.58-3a10.53,10.53,0,0,1,11.6,11.23Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
+ <path id="Path_320" data-name="Path 320" d="M429.08,725.44v-.84l175.62-1.91,107-1h.3v-.17l1-13.44.43-6,1.64-22.61,1.29-17.9v-.44a10.617,10.617,0,0,0-.11-2.47.3.3,0,0,0,0-.1,10.391,10.391,0,0,0-2-4.64,10.54,10.54,0,0,0-9.42-4c-12.11,1.24-24.34,2.23-36.58,3-1.67.12-3.34.22-5,.31-78.94,4.69-157.17.89-199.5-2l-3-.22c-7.33-.52-13.46-1-18.18-1.41a10.54,10.54,0,0,0-11.24,8.53,11,11,0,0,0-.18,1.64l-.68,22.16L429.54,710l-.44,14.36v1.12Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
+ <path id="Path_321" data-name="Path 321" d="M716.67,664.18l-1.23,15.33-1.83,22.85-.46,5.72-1,12.81-.06.64v.17h0l-.15,1.48.11-1.48h-.29l-107,1-175.65,1.9v-.28l.49-14.36,1-28.06.64-18.65A6.36,6.36,0,0,1,434.3,658a6.25,6.25,0,0,1,3.78-.9c2.1.17,4.68.37,7.69.59,4.89.36,10.92.78,17.94,1.22,13,.82,29.31,1.7,48,2.42,52,2,122.2,2.67,188.88-3.17,3-.26,6.1-.55,9.13-.84a6.26,6.26,0,0,1,3.48.66,5.159,5.159,0,0,1,.86.54,6.14,6.14,0,0,1,2,2.46,3.564,3.564,0,0,1,.25.61A6.279,6.279,0,0,1,716.67,664.18Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
+ <path id="Path_322" data-name="Path 322" d="M377.44,677.87v3.19a6.13,6.13,0,0,1-3.5,5.54l-40.1.77a6.12,6.12,0,0,1-3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
+ <path id="Path_323" data-name="Path 323" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
+ <path id="Path_324" data-name="Path 324" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" opacity="0.1"/>
+ <path id="Path_325" data-name="Path 325" d="M300.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
+ <path id="Path_326" data-name="Path 326" d="M758.56,679.87v3.19a6.13,6.13,0,0,0,3.5,5.54l40.1.77a6.12,6.12,0,0,0,3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
+ <path id="Path_327" data-name="Path 327" d="M678.72,517.57l52.25,1V509.9l-52.25-1Z" opacity="0.1"/>
+ <path id="Path_328" data-name="Path 328" d="M676.72,517.57l52.25,1V509.9l-52.25-1Z" fill="#3f3d56"/>
+ <path id="Path_329" data-name="Path 329" d="M534.13,486.79c.08,7-3.16,13.6-5.91,20.07a163.491,163.491,0,0,0-12.66,74.71c.73,11,2.58,22,.73,32.9s-8.43,21.77-19,24.9c17.53,10.45,41.26,9.35,57.76-2.66,8.79-6.4,15.34-15.33,21.75-24.11a97.86,97.86,0,0,1-13.31,44.75A103.43,103.43,0,0,0,637,616.53c4.31-5.81,8.06-12.19,9.72-19.23,3.09-13-1.22-26.51-4.51-39.5a266.055,266.055,0,0,1-6.17-33c-.43-3.56-.78-7.22.1-10.7,1-4.07,3.67-7.51,5.64-11.22,5.6-10.54,5.73-23.3,2.86-34.88s-8.49-22.26-14.06-32.81c-4.46-8.46-9.3-17.31-17.46-22.28-5.1-3.1-11-4.39-16.88-5.64l-25.37-5.43c-5.55-1.19-11.26-2.38-16.87-1.51-9.47,1.48-16.14,8.32-22,15.34-4.59,5.46-15.81,15.71-16.6,22.86-.72,6.59,5.1,17.63,6.09,24.58,1.3,9,2.22,6,7.3,11.52C532,478.05,534.07,482,534.13,486.79Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
+ </g>
+ <g id="docusaurus_keytar" transform="translate(670.271 615.768)">
+ <path id="Path_40" data-name="Path 40" d="M99,52h43.635V69.662H99Z" transform="translate(-49.132 -33.936)" fill="#fff" fill-rule="evenodd"/>
+ <path id="Path_41" data-name="Path 41" d="M13.389,158.195A10.377,10.377,0,0,1,4.4,153a10.377,10.377,0,0,0,8.988,15.584H23.779V158.195Z" transform="translate(-3 -82.47)" fill="#3ecc5f" fill-rule="evenodd"/>
+ <path id="Path_42" data-name="Path 42" d="M66.967,38.083l36.373-2.273V30.615A10.389,10.389,0,0,0,92.95,20.226H46.2l-1.3-2.249a1.5,1.5,0,0,0-2.6,0L41,20.226l-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-.034,0-2.152-2.151a1.5,1.5,0,0,0-2.508.672L25.21,21.4l-2.7-.723a1.5,1.5,0,0,0-1.836,1.837l.722,2.7-2.65.71a1.5,1.5,0,0,0-.673,2.509l2.152,2.152c0,.011,0,.022,0,.033l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6L20.226,41l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3A10.389,10.389,0,0,0,30.615,103.34H92.95A10.389,10.389,0,0,0,103.34,92.95V51.393L66.967,49.12a5.53,5.53,0,0,1,0-11.038" transform="translate(-9.836 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
+ <path id="Path_43" data-name="Path 43" d="M143,163.779h15.584V143H143Z" transform="translate(-70.275 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
+ <path id="Path_44" data-name="Path 44" d="M173.779,148.389a2.582,2.582,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-75.08 -75.262)" fill="#44d860" fill-rule="evenodd"/>
+ <path id="Path_45" data-name="Path 45" d="M153,113.389h15.584V103H153Z" transform="translate(-75.08 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
+ <path id="Path_46" data-name="Path 46" d="M183.389,108.944a1.3,1.3,0,1,0,0-2.6,1.336,1.336,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.337,1.337,0,0,0,.166.017" transform="translate(-84.691 -57.894)" fill="#44d860" fill-rule="evenodd"/>
+ <path id="Path_47" data-name="Path 47" d="M52.188,48.292a1.3,1.3,0,0,1-1.3-1.3,3.9,3.9,0,0,0-7.792,0,1.3,1.3,0,1,1-2.6,0,6.493,6.493,0,0,1,12.987,0,1.3,1.3,0,0,1-1.3,1.3" transform="translate(-21.02 -28.41)" fill-rule="evenodd"/>
+ <path id="Path_48" data-name="Path 48" d="M103,139.752h31.168a10.389,10.389,0,0,0,10.389-10.389V93H113.389A10.389,10.389,0,0,0,103,103.389Z" transform="translate(-51.054 -53.638)" fill="#ffff50" fill-rule="evenodd"/>
+ <path id="Path_49" data-name="Path 49" d="M141.1,94.017H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0-25.877H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.293H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m7.782-47.993c-.006,0-.011,0-.018,0-1.605.055-2.365,1.66-3.035,3.077-.7,1.48-1.24,2.443-2.126,2.414-.981-.035-1.542-1.144-2.137-2.317-.683-1.347-1.462-2.876-3.1-2.819-1.582.054-2.344,1.451-3.017,2.684-.715,1.313-1.2,2.112-2.141,2.075-1-.036-1.533-.938-2.149-1.981-.686-1.162-1.479-2.467-3.084-2.423-1.555.053-2.319,1.239-2.994,2.286-.713,1.106-1.213,1.781-2.164,1.741-1.025-.036-1.554-.784-2.167-1.65-.688-.973-1.463-2.074-3.062-2.021a3.815,3.815,0,0,0-2.959,1.879c-.64.812-1.14,1.456-2.2,1.415a.52.52,0,0,0-.037,1.039,3.588,3.588,0,0,0,3.05-1.811c.611-.777,1.139-1.448,2.178-1.483,1-.043,1.47.579,2.179,1.582.674.953,1.438,2.033,2.977,2.089,1.612.054,2.387-1.151,3.074-2.217.614-.953,1.144-1.775,2.156-1.81.931-.035,1.438.7,2.153,1.912.674,1.141,1.437,2.434,3.006,2.491,1.623.056,2.407-1.361,3.09-2.616.592-1.085,1.15-2.109,2.14-2.143.931-.022,1.417.829,2.135,2.249.671,1.326,1.432,2.828,3.026,2.886l.088,0c1.592,0,2.347-1.6,3.015-3.01.592-1.252,1.152-2.431,2.113-2.479Z" transform="translate(-55.378 -38.552)" fill-rule="evenodd"/>
+ <path id="Path_50" data-name="Path 50" d="M83,163.779h20.779V143H83Z" transform="translate(-41.443 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
+ <g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 51.971, 43.3)">
+ <rect id="Rectangle_3" data-name="Rectangle 3" width="43.906" height="17.333" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
+ <g id="Group_2" data-name="Group 2" transform="translate(0.728 10.948)">
+ <rect id="Rectangle_4" data-name="Rectangle 4" width="2.537" height="2.537" rx="1" transform="translate(7.985 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_5" data-name="Rectangle 5" width="2.537" height="2.537" rx="1" transform="translate(10.991 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_6" data-name="Rectangle 6" width="2.537" height="2.537" rx="1" transform="translate(13.997 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_7" data-name="Rectangle 7" width="2.537" height="2.537" rx="1" transform="translate(17.003 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_8" data-name="Rectangle 8" width="2.537" height="2.537" rx="1" transform="translate(20.009 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_9" data-name="Rectangle 9" width="2.537" height="2.537" rx="1" transform="translate(23.015 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_10" data-name="Rectangle 10" width="2.537" height="2.537" rx="1" transform="translate(26.021 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_11" data-name="Rectangle 11" width="2.537" height="2.537" rx="1" transform="translate(29.028 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_12" data-name="Rectangle 12" width="2.537" height="2.537" rx="1" transform="translate(32.034 0)" fill="#4a4a4a"/>
+ <path id="Path_51" data-name="Path 51" d="M.519,0H6.9A.519.519,0,0,1,7.421.52v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0ZM35.653,0h6.383a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H35.652a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,35.652,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
+ </g>
+ <g id="Group_3" data-name="Group 3" transform="translate(0.728 4.878)">
+ <path id="Path_52" data-name="Path 52" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
+ <rect id="Rectangle_13" data-name="Rectangle 13" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_14" data-name="Rectangle 14" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_15" data-name="Rectangle 15" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_16" data-name="Rectangle 16" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_17" data-name="Rectangle 17" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_18" data-name="Rectangle 18" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_19" data-name="Rectangle 19" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_20" data-name="Rectangle 20" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_21" data-name="Rectangle 21" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_22" data-name="Rectangle 22" width="2.537" height="2.537" rx="1" transform="translate(31 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_23" data-name="Rectangle 23" width="2.537" height="2.537" rx="1" transform="translate(34.006 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_24" data-name="Rectangle 24" width="2.537" height="2.537" rx="1" transform="translate(37.012 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_25" data-name="Rectangle 25" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
+ </g>
+ <g id="Group_4" data-name="Group 4" transform="translate(43.283 4.538) rotate(180)">
+ <path id="Path_53" data-name="Path 53" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
+ <rect id="Rectangle_26" data-name="Rectangle 26" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_27" data-name="Rectangle 27" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_28" data-name="Rectangle 28" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_29" data-name="Rectangle 29" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_30" data-name="Rectangle 30" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_31" data-name="Rectangle 31" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_32" data-name="Rectangle 32" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_33" data-name="Rectangle 33" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_34" data-name="Rectangle 34" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_35" data-name="Rectangle 35" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_36" data-name="Rectangle 36" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_37" data-name="Rectangle 37" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_38" data-name="Rectangle 38" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_39" data-name="Rectangle 39" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_40" data-name="Rectangle 40" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_41" data-name="Rectangle 41" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_42" data-name="Rectangle 42" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_43" data-name="Rectangle 43" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_44" data-name="Rectangle 44" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_45" data-name="Rectangle 45" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_46" data-name="Rectangle 46" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_47" data-name="Rectangle 47" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_48" data-name="Rectangle 48" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_49" data-name="Rectangle 49" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_50" data-name="Rectangle 50" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_51" data-name="Rectangle 51" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
+ </g>
+ <g id="Group_6" data-name="Group 6" transform="translate(0.728 7.883)">
+ <path id="Path_54" data-name="Path 54" d="M.519,0h3.47a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
+ <g id="Group_5" data-name="Group 5" transform="translate(5.073 0)">
+ <rect id="Rectangle_52" data-name="Rectangle 52" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_53" data-name="Rectangle 53" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_54" data-name="Rectangle 54" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_55" data-name="Rectangle 55" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_56" data-name="Rectangle 56" width="2.537" height="2.537" rx="1" transform="translate(12.025 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_57" data-name="Rectangle 57" width="2.537" height="2.537" rx="1" transform="translate(15.031 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_58" data-name="Rectangle 58" width="2.537" height="2.537" rx="1" transform="translate(18.037 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_59" data-name="Rectangle 59" width="2.537" height="2.537" rx="1" transform="translate(21.042 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_60" data-name="Rectangle 60" width="2.537" height="2.537" rx="1" transform="translate(24.049 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_61" data-name="Rectangle 61" width="2.537" height="2.537" rx="1" transform="translate(27.055 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_62" data-name="Rectangle 62" width="2.537" height="2.537" rx="1" transform="translate(30.061 0)" fill="#4a4a4a"/>
+ </g>
+ <path id="Path_55" data-name="Path 55" d="M.52,0H3.8a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(38.234 0)" fill="#4a4a4a" fill-rule="evenodd"/>
+ </g>
+ <g id="Group_7" data-name="Group 7" transform="translate(0.728 14.084)">
+ <rect id="Rectangle_63" data-name="Rectangle 63" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_64" data-name="Rectangle 64" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_65" data-name="Rectangle 65" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_66" data-name="Rectangle 66" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
+ <path id="Path_56" data-name="Path 56" d="M.519,0H14.981A.519.519,0,0,1,15.5.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.018V.519A.519.519,0,0,1,.519,0Zm15.97,0h1.874a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H16.489a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,16.489,0Z" transform="translate(12.024 0)" fill="#4a4a4a" fill-rule="evenodd"/>
+ <rect id="Rectangle_67" data-name="Rectangle 67" width="2.537" height="2.537" rx="1" transform="translate(31.376 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_68" data-name="Rectangle 68" width="2.537" height="2.537" rx="1" transform="translate(34.382 0)" fill="#4a4a4a"/>
+ <rect id="Rectangle_69" data-name="Rectangle 69" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
+ <path id="Path_57" data-name="Path 57" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(39.736 1.08) rotate(180)" fill="#4a4a4a"/>
+ <path id="Path_58" data-name="Path 58" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(37.2 1.456)" fill="#4a4a4a"/>
+ </g>
+ <rect id="Rectangle_70" data-name="Rectangle 70" width="42.273" height="1.127" rx="0.564" transform="translate(0.915 0.556)" fill="#4a4a4a"/>
+ <rect id="Rectangle_71" data-name="Rectangle 71" width="2.37" height="0.752" rx="0.376" transform="translate(1.949 0.744)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_72" data-name="Rectangle 72" width="2.37" height="0.752" rx="0.376" transform="translate(5.193 0.744)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_73" data-name="Rectangle 73" width="2.37" height="0.752" rx="0.376" transform="translate(7.688 0.744)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_74" data-name="Rectangle 74" width="2.37" height="0.752" rx="0.376" transform="translate(10.183 0.744)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_75" data-name="Rectangle 75" width="2.37" height="0.752" rx="0.376" transform="translate(12.679 0.744)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_76" data-name="Rectangle 76" width="2.37" height="0.752" rx="0.376" transform="translate(15.797 0.744)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_77" data-name="Rectangle 77" width="2.37" height="0.752" rx="0.376" transform="translate(18.292 0.744)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_78" data-name="Rectangle 78" width="2.37" height="0.752" rx="0.376" transform="translate(20.788 0.744)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_79" data-name="Rectangle 79" width="2.37" height="0.752" rx="0.376" transform="translate(23.283 0.744)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_80" data-name="Rectangle 80" width="2.37" height="0.752" rx="0.376" transform="translate(26.402 0.744)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_81" data-name="Rectangle 81" width="2.37" height="0.752" rx="0.376" transform="translate(28.897 0.744)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_82" data-name="Rectangle 82" width="2.37" height="0.752" rx="0.376" transform="translate(31.393 0.744)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_83" data-name="Rectangle 83" width="2.37" height="0.752" rx="0.376" transform="translate(34.512 0.744)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_84" data-name="Rectangle 84" width="2.37" height="0.752" rx="0.376" transform="translate(37.007 0.744)" fill="#d8d8d8" opacity="0.136"/>
+ <rect id="Rectangle_85" data-name="Rectangle 85" width="2.37" height="0.752" rx="0.376" transform="translate(39.502 0.744)" fill="#d8d8d8" opacity="0.136"/>
+ </g>
+ <path id="Path_59" data-name="Path 59" d="M123.779,148.389a2.583,2.583,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-51.054 -75.262)" fill="#44d860" fill-rule="evenodd"/>
+ <path id="Path_60" data-name="Path 60" d="M83,113.389h20.779V103H83Z" transform="translate(-41.443 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
+ <path id="Path_61" data-name="Path 61" d="M123.389,108.944a1.3,1.3,0,1,0,0-2.6,1.338,1.338,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.335,1.335,0,0,0,.166.017" transform="translate(-55.859 -57.894)" fill="#44d860" fill-rule="evenodd"/>
+ <path id="Path_62" data-name="Path 62" d="M141.8,38.745a1.41,1.41,0,0,1-.255-.026,1.309,1.309,0,0,1-.244-.073,1.349,1.349,0,0,1-.224-.119,1.967,1.967,0,0,1-.2-.161,1.52,1.52,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.41,1.41,0,0,1,.026-.255,1.5,1.5,0,0,1,.072-.244,1.364,1.364,0,0,1,.12-.223,1.252,1.252,0,0,1,.358-.358,1.349,1.349,0,0,1,.224-.119,1.309,1.309,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.968,1.968,0,0,1,.2.161,1.908,1.908,0,0,1,.161.2,1.322,1.322,0,0,1,.12.223,1.361,1.361,0,0,1,.1.5,1.317,1.317,0,0,1-.379.919,1.968,1.968,0,0,1-.2.161,1.346,1.346,0,0,1-.223.119,1.332,1.332,0,0,1-.5.1m10.389-.649a1.326,1.326,0,0,1-.92-.379,1.979,1.979,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.326,1.326,0,0,1,.379-.919,1.967,1.967,0,0,1,.2-.161,1.351,1.351,0,0,1,.224-.119,1.308,1.308,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.967,1.967,0,0,1,.2.161,1.326,1.326,0,0,1,.379.919,1.461,1.461,0,0,1-.026.255,1.323,1.323,0,0,1-.073.244,1.847,1.847,0,0,1-.119.223,1.911,1.911,0,0,1-.161.2,1.967,1.967,0,0,1-.2.161,1.294,1.294,0,0,1-.722.218" transform="translate(-69.074 -26.006)" fill-rule="evenodd"/>
+ </g>
+ <g id="React-icon" transform="translate(906.3 541.56)">
+ <path id="Path_330" data-name="Path 330" d="M263.668,117.179c0-5.827-7.3-11.35-18.487-14.775,2.582-11.4,1.434-20.477-3.622-23.382a7.861,7.861,0,0,0-4.016-1v4a4.152,4.152,0,0,1,2.044.466c2.439,1.4,3.5,6.724,2.672,13.574-.2,1.685-.52,3.461-.914,5.272a86.9,86.9,0,0,0-11.386-1.954,87.469,87.469,0,0,0-7.459-8.965c5.845-5.433,11.332-8.41,15.062-8.41V78h0c-4.931,0-11.386,3.514-17.913,9.611-6.527-6.061-12.982-9.539-17.913-9.539v4c3.712,0,9.216,2.959,15.062,8.356a84.687,84.687,0,0,0-7.405,8.947,83.732,83.732,0,0,0-11.4,1.972c-.412-1.793-.717-3.532-.932-5.2-.843-6.85.2-12.175,2.618-13.592a3.991,3.991,0,0,1,2.062-.466v-4h0a8,8,0,0,0-4.052,1c-5.039,2.9-6.168,11.96-3.568,23.328-11.153,3.443-18.415,8.947-18.415,14.757,0,5.828,7.3,11.35,18.487,14.775-2.582,11.4-1.434,20.477,3.622,23.382a7.882,7.882,0,0,0,4.034,1c4.931,0,11.386-3.514,17.913-9.611,6.527,6.061,12.982,9.539,17.913,9.539a8,8,0,0,0,4.052-1c5.039-2.9,6.168-11.96,3.568-23.328C256.406,128.511,263.668,122.988,263.668,117.179Zm-23.346-11.96c-.663,2.313-1.488,4.7-2.421,7.083-.735-1.434-1.506-2.869-2.349-4.3-.825-1.434-1.7-2.833-2.582-4.2C235.517,104.179,237.974,104.645,240.323,105.219Zm-8.212,19.1c-1.4,2.421-2.833,4.716-4.321,6.85-2.672.233-5.379.359-8.1.359-2.708,0-5.415-.126-8.069-.341q-2.232-3.2-4.339-6.814-2.044-3.523-3.73-7.136c1.112-2.4,2.367-4.805,3.712-7.154,1.4-2.421,2.833-4.716,4.321-6.85,2.672-.233,5.379-.359,8.1-.359,2.708,0,5.415.126,8.069.341q2.232,3.2,4.339,6.814,2.044,3.523,3.73,7.136C234.692,119.564,233.455,121.966,232.11,124.315Zm5.792-2.331c.968,2.4,1.793,4.805,2.474,7.136-2.349.574-4.823,1.058-7.387,1.434.879-1.381,1.757-2.8,2.582-4.25C236.4,124.871,237.167,123.419,237.9,121.984ZM219.72,141.116a73.921,73.921,0,0,1-4.985-5.738c1.614.072,3.263.126,4.931.126,1.685,0,3.353-.036,4.985-.126A69.993,69.993,0,0,1,219.72,141.116ZM206.38,130.555c-2.546-.377-5-.843-7.352-1.417.663-2.313,1.488-4.7,2.421-7.083.735,1.434,1.506,2.869,2.349,4.3S205.5,129.192,206.38,130.555ZM219.63,93.241a73.924,73.924,0,0,1,4.985,5.738c-1.614-.072-3.263-.126-4.931-.126-1.686,0-3.353.036-4.985.126A69.993,69.993,0,0,1,219.63,93.241ZM206.362,103.8c-.879,1.381-1.757,2.8-2.582,4.25-.825,1.434-1.6,2.869-2.331,4.3-.968-2.4-1.793-4.805-2.474-7.136C201.323,104.663,203.8,104.179,206.362,103.8Zm-16.227,22.449c-6.348-2.708-10.454-6.258-10.454-9.073s4.106-6.383,10.454-9.073c1.542-.663,3.228-1.255,4.967-1.811a86.122,86.122,0,0,0,4.034,10.92,84.9,84.9,0,0,0-3.981,10.866C193.38,127.525,191.694,126.915,190.134,126.252Zm9.647,25.623c-2.439-1.4-3.5-6.724-2.672-13.574.2-1.686.52-3.461.914-5.272a86.9,86.9,0,0,0,11.386,1.954,87.465,87.465,0,0,0,7.459,8.965c-5.845,5.433-11.332,8.41-15.062,8.41A4.279,4.279,0,0,1,199.781,151.875Zm42.532-13.663c.843,6.85-.2,12.175-2.618,13.592a3.99,3.99,0,0,1-2.062.466c-3.712,0-9.216-2.959-15.062-8.356a84.689,84.689,0,0,0,7.405-8.947,83.731,83.731,0,0,0,11.4-1.972A50.194,50.194,0,0,1,242.313,138.212Zm6.9-11.96c-1.542.663-3.228,1.255-4.967,1.811a86.12,86.12,0,0,0-4.034-10.92,84.9,84.9,0,0,0,3.981-10.866c1.775.556,3.461,1.165,5.039,1.829,6.348,2.708,10.454,6.258,10.454,9.073C259.67,119.994,255.564,123.562,249.216,126.252Z" fill="#61dafb"/>
+ <path id="Path_331" data-name="Path 331" d="M320.8,78.4Z" transform="translate(-119.082 -0.328)" fill="#61dafb"/>
+ <circle id="Ellipse_112" data-name="Ellipse 112" cx="8.194" cy="8.194" r="8.194" transform="translate(211.472 108.984)" fill="#61dafb"/>
+ <path id="Path_332" data-name="Path 332" d="M520.5,78.1Z" transform="translate(-282.975 -0.082)" fill="#61dafb"/>
+ </g>
+ </g>
+</svg>
diff --git a/docs/static/img/undraw_docusaurus_tree.svg b/docs/static/img/undraw_docusaurus_tree.svg
new file mode 100644
index 0000000..a05cc03
--- /dev/null
+++ b/docs/static/img/undraw_docusaurus_tree.svg
@@ -0,0 +1 @@
+<svg id="ac356da0-b129-4ca5-aecc-4700531dd101" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="1129" height="663" viewBox="0 0 1129 663"><title>docu_tree</title><circle cx="321" cy="321" r="321" fill="#f2f2f2"/><ellipse cx="559" cy="635.49998" rx="514" ry="27.50002" fill="#3f3d56"/><ellipse cx="558" cy="627" rx="460" ry="22" opacity="0.2"/><rect x="131" y="152.5" width="840" height="50" fill="#3f3d56"/><path d="M166.5,727.3299A21.67009,21.67009,0,0,0,188.1701,749H984.8299A21.67009,21.67009,0,0,0,1006.5,727.3299V296h-840Z" transform="translate(-35.5 -118.5)" fill="#3f3d56"/><path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" fill="#3f3d56"/><path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" opacity="0.2"/><circle cx="181" cy="147.5" r="13" fill="#3f3d56"/><circle cx="217" cy="147.5" r="13" fill="#3f3d56"/><circle cx="253" cy="147.5" r="13" fill="#3f3d56"/><rect x="168" y="213.5" width="337" height="386" rx="5.33505" fill="#606060"/><rect x="603" y="272.5" width="284" height="22" rx="5.47638" fill="#2e8555"/><rect x="537" y="352.5" width="416" height="15" rx="5.47638" fill="#2e8555"/><rect x="537" y="396.5" width="416" height="15" rx="5.47638" fill="#2e8555"/><rect x="537" y="440.5" width="416" height="15" rx="5.47638" fill="#2e8555"/><rect x="537" y="484.5" width="416" height="15" rx="5.47638" fill="#2e8555"/><rect x="865" y="552.5" width="88" height="26" rx="7.02756" fill="#3ecc5f"/><path d="M1088.60287,624.61594a30.11371,30.11371,0,0,0,3.98291-15.266c0-13.79652-8.54358-24.98081-19.08256-24.98081s-19.08256,11.18429-19.08256,24.98081a30.11411,30.11411,0,0,0,3.98291,15.266,31.248,31.248,0,0,0,0,30.53213,31.248,31.248,0,0,0,0,30.53208,31.248,31.248,0,0,0,0,30.53208,30.11408,30.11408,0,0,0-3.98291,15.266c0,13.79652,8.54353,24.98081,19.08256,24.98081s19.08256-11.18429,19.08256-24.98081a30.11368,30.11368,0,0,0-3.98291-15.266,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53213Z" transform="translate(-35.5 -118.5)" fill="#3f3d56"/><ellipse cx="1038.00321" cy="460.31783" rx="19.08256" ry="24.9808" fill="#3f3d56"/><ellipse cx="1038.00321" cy="429.78574" rx="19.08256" ry="24.9808" fill="#3f3d56"/><path d="M1144.93871,339.34489a91.61081,91.61081,0,0,0,7.10658-10.46092l-50.141-8.23491,54.22885.4033a91.566,91.566,0,0,0,1.74556-72.42605l-72.75449,37.74139,67.09658-49.32086a91.41255,91.41255,0,1,0-150.971,102.29805,91.45842,91.45842,0,0,0-10.42451,16.66946l65.0866,33.81447-69.40046-23.292a91.46011,91.46011,0,0,0,14.73837,85.83669,91.40575,91.40575,0,1,0,143.68892,0,91.41808,91.41808,0,0,0,0-113.02862Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd"/><path d="M981.6885,395.8592a91.01343,91.01343,0,0,0,19.56129,56.51431,91.40575,91.40575,0,1,0,143.68892,0C1157.18982,436.82067,981.6885,385.60008,981.6885,395.8592Z" transform="translate(-35.5 -118.5)" opacity="0.1"/><path d="M365.62,461.43628H477.094v45.12043H365.62Z" transform="translate(-35.5 -118.5)" fill="#fff" fill-rule="evenodd"/><path d="M264.76252,608.74122a26.50931,26.50931,0,0,1-22.96231-13.27072,26.50976,26.50976,0,0,0,22.96231,39.81215H291.304V608.74122Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd"/><path d="M384.17242,468.57061l92.92155-5.80726V449.49263a26.54091,26.54091,0,0,0-26.54143-26.54143H331.1161l-3.31768-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622-3.31767-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622L301.257,417.205a3.83043,3.83043,0,0,0-6.63536,0L291.304,422.9512c-.02919,0-.05573.004-.08625.004l-5.49674-5.49541a3.8293,3.8293,0,0,0-6.4071,1.71723l-1.81676,6.77338L270.607,424.1031a3.82993,3.82993,0,0,0-4.6912,4.69253l1.84463,6.89148-6.77072,1.81411a3.8315,3.8315,0,0,0-1.71988,6.40975l5.49673,5.49673c0,.02787-.004.05574-.004.08493l-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74621,3.31768L259.0163,466.081a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768L259.0163,558.976a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768A26.54091,26.54091,0,0,0,291.304,635.28265H450.55254A26.5409,26.5409,0,0,0,477.094,608.74122V502.5755l-92.92155-5.80727a14.12639,14.12639,0,0,1,0-28.19762" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd"/><path d="M424.01111,635.28265h39.81214V582.19979H424.01111Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd"/><path d="M490.36468,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15393-.59852A6.62668,6.62668,0,1,0,482.80568,590.21q-.2203-.22491-.44457-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39414-.10218-.59056-.15262a6.63957,6.63957,0,1,0-13.10086,0c-.1964.05042-.39414.09687-.59056.15262a6.62767,6.62767,0,1,0-11.39688,6.56369,26.52754,26.52754,0,1,0,44.23127,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd"/><path d="M437.28182,555.65836H477.094V529.11693H437.28182Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd"/><path d="M490.36468,545.70532a3.31768,3.31768,0,0,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd"/><path d="M317.84538,466.081a3.31768,3.31768,0,0,1-3.31767-3.31768,9.953,9.953,0,1,0-19.90608,0,3.31768,3.31768,0,1,1-6.63535,0,16.58839,16.58839,0,1,1,33.17678,0,3.31768,3.31768,0,0,1-3.31768,3.31768" transform="translate(-35.5 -118.5)" fill-rule="evenodd"/><path d="M370.92825,635.28265h79.62429A26.5409,26.5409,0,0,0,477.094,608.74122v-92.895H397.46968a26.54091,26.54091,0,0,0-26.54143,26.54143Z" transform="translate(-35.5 -118.5)" fill="#ffff50" fill-rule="evenodd"/><path d="M457.21444,556.98543H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0-66.10674H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.29459H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414M477.094,474.19076c-.01592,0-.0292-.008-.04512-.00663-4.10064.13934-6.04083,4.24132-7.75274,7.86024-1.78623,3.78215-3.16771,6.24122-5.43171,6.16691-2.50685-.09024-3.94007-2.92222-5.45825-5.91874-1.74377-3.44243-3.73438-7.34667-7.91333-7.20069-4.04227.138-5.98907,3.70784-7.70631,6.857-1.82738,3.35484-3.07084,5.39455-5.46887,5.30033-2.55727-.09289-3.91619-2.39536-5.48877-5.06013-1.75306-2.96733-3.77951-6.30359-7.8775-6.18946-3.97326.13669-5.92537,3.16507-7.64791,5.83912-1.82207,2.82666-3.09872,4.5492-5.52725,4.447-2.61832-.09289-3.9706-2.00388-5.53522-4.21611-1.757-2.4856-3.737-5.299-7.82308-5.16231-3.88567.13271-5.83779,2.61434-7.559,4.80135-1.635,2.07555-2.9116,3.71846-5.61218,3.615a1.32793,1.32793,0,1,0-.09555,2.65414c4.00377.134,6.03154-2.38873,7.79257-4.6275,1.562-1.9853,2.91027-3.69855,5.56441-3.78879,2.55594-.10882,3.75429,1.47968,5.56707,4.04093,1.7212,2.43385,3.67465,5.19416,7.60545,5.33616,4.11789.138,6.09921-2.93946,7.8536-5.66261,1.56861-2.43385,2.92221-4.53461,5.50734-4.62352,2.37944-.08892,3.67466,1.79154,5.50072,4.885,1.72121,2.91557,3.67069,6.21865,7.67977,6.36463,4.14709.14332,6.14965-3.47693,7.89475-6.68181,1.51155-2.77092,2.93814-5.38791,5.46621-5.4755,2.37944-.05573,3.62025,2.11668,5.45558,5.74622,1.71459,3.388,3.65875,7.22591,7.73019,7.37321l.22429.004c4.06614,0,5.99571-4.08074,7.70364-7.68905,1.51154-3.19825,2.94211-6.21069,5.3972-6.33411Z" transform="translate(-35.5 -118.5)" fill-rule="evenodd"/><path d="M344.38682,635.28265h53.08286V582.19979H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd"/><path d="M424.01111,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15394-.59852A6.62667,6.62667,0,1,0,416.45211,590.21q-.2203-.22491-.44458-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39413-.10218-.59054-.15262a6.63957,6.63957,0,1,0-13.10084,0c-.19641.05042-.39414.09687-.59055.15262a6.62767,6.62767,0,1,0-11.39689,6.56369,26.52755,26.52755,0,1,0,44.2313,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd"/><path d="M344.38682,555.65836h53.08286V529.11693H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd"/><path d="M410.74039,545.70532a3.31768,3.31768,0,1,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd"/><path d="M424.01111,447.8338a3.60349,3.60349,0,0,1-.65028-.06636,3.34415,3.34415,0,0,1-.62372-.18579,3.44679,3.44679,0,0,1-.572-.30522,5.02708,5.02708,0,0,1-.50429-.4114,3.88726,3.88726,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.60248,3.60248,0,0,1,.06636-.65027,3.82638,3.82638,0,0,1,.18447-.62373,3.48858,3.48858,0,0,1,.30656-.57064,3.197,3.197,0,0,1,.91436-.91568,3.44685,3.44685,0,0,1,.572-.30523,3.344,3.344,0,0,1,.62372-.18578,3.06907,3.06907,0,0,1,1.30053,0,3.22332,3.22332,0,0,1,1.19436.491,5.02835,5.02835,0,0,1,.50429.41139,4.8801,4.8801,0,0,1,.41139.50429,3.38246,3.38246,0,0,1,.30522.57064,3.47806,3.47806,0,0,1,.25215,1.274A3.36394,3.36394,0,0,1,426.36,446.865a5.02708,5.02708,0,0,1-.50429.4114,3.3057,3.3057,0,0,1-1.84463.55737m26.54143-1.65884a3.38754,3.38754,0,0,1-2.35024-.96877,5.04185,5.04185,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.38659,3.38659,0,0,1,.96744-2.34892,5.02559,5.02559,0,0,1,.50429-.41139,3.44685,3.44685,0,0,1,.572-.30523,3.3432,3.3432,0,0,1,.62373-.18579,3.06952,3.06952,0,0,1,1.30052,0,3.22356,3.22356,0,0,1,1.19436.491,5.02559,5.02559,0,0,1,.50429.41139,3.38792,3.38792,0,0,1,.96876,2.34892,3.72635,3.72635,0,0,1-.06636.65026,3.37387,3.37387,0,0,1-.18579.62373,4.71469,4.71469,0,0,1-.30522.57064,4.8801,4.8801,0,0,1-.41139.50429,5.02559,5.02559,0,0,1-.50429.41139,3.30547,3.30547,0,0,1-1.84463.55737" transform="translate(-35.5 -118.5)" fill-rule="evenodd"/></svg> \ No newline at end of file
diff --git a/docs/static/pdf/entwicklerworkshop2013-activerecord.pdf b/docs/static/pdf/entwicklerworkshop2013-activerecord.pdf
new file mode 100644
index 0000000..288e5fd
--- /dev/null
+++ b/docs/static/pdf/entwicklerworkshop2013-activerecord.pdf
Binary files differ
diff --git a/docs/static/pdf/entwicklerworkshop2014-attack_of_the_sorm.pdf b/docs/static/pdf/entwicklerworkshop2014-attack_of_the_sorm.pdf
new file mode 100644
index 0000000..78846f1
--- /dev/null
+++ b/docs/static/pdf/entwicklerworkshop2014-attack_of_the_sorm.pdf
Binary files differ
diff --git a/docs/static/pdf/entwicklerworkshop2015-sorm_sucks.pdf b/docs/static/pdf/entwicklerworkshop2015-sorm_sucks.pdf
new file mode 100644
index 0000000..9c7276e
--- /dev/null
+++ b/docs/static/pdf/entwicklerworkshop2015-sorm_sucks.pdf
Binary files differ
diff --git a/docs/yarn.lock b/docs/yarn.lock
new file mode 100644
index 0000000..8f707ea
--- /dev/null
+++ b/docs/yarn.lock
@@ -0,0 +1,8208 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@algolia/autocomplete-core@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.11.1.tgz#73dce6a65430a872cd9474babf052dcb3ca1d6fe"
+ integrity sha512-C4ZaUbwNHOkbXM+vsUpx9AYhfLRCcku4tjn64Dr6/WjBhD1gv/WcI/GlvTc7QU53xPubNm8pfnfFAjRogEdnNQ==
+ dependencies:
+ "@algolia/autocomplete-plugin-algolia-insights" "1.11.1"
+ "@algolia/autocomplete-shared" "1.11.1"
+
+"@algolia/autocomplete-core@1.9.3":
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz#1d56482a768c33aae0868c8533049e02e8961be7"
+ integrity sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==
+ dependencies:
+ "@algolia/autocomplete-plugin-algolia-insights" "1.9.3"
+ "@algolia/autocomplete-shared" "1.9.3"
+
+"@algolia/autocomplete-js@^1.8.2":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@algolia/autocomplete-js/-/autocomplete-js-1.11.1.tgz#e9cfc4ae3f3e282add78d723f025be361ddbb685"
+ integrity sha512-Oqus5IAz/rGubXvUcGQyhSwFr/KmfHxrmw/u+3pqWWhgErRIF/LQmHO6/+Q4pu21EOAMdKw1p/gSel68e5AaCA==
+ dependencies:
+ "@algolia/autocomplete-core" "1.11.1"
+ "@algolia/autocomplete-preset-algolia" "1.11.1"
+ "@algolia/autocomplete-shared" "1.11.1"
+ htm "^3.1.1"
+ preact "^10.13.2"
+
+"@algolia/autocomplete-plugin-algolia-insights@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.11.1.tgz#46e7d7c75a10e33ca6310ecc920a19a2fed13570"
+ integrity sha512-Ajaav4irJrbwLuQ0hYuaZlUH1pY7iobXSFfQsHFSQ+m2Q8r/h1GtkaiRCpcfnwO8CURdcD3RFMc0pClOPzmJeA==
+ dependencies:
+ "@algolia/autocomplete-shared" "1.11.1"
+
+"@algolia/autocomplete-plugin-algolia-insights@1.9.3":
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz#9b7f8641052c8ead6d66c1623d444cbe19dde587"
+ integrity sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==
+ dependencies:
+ "@algolia/autocomplete-shared" "1.9.3"
+
+"@algolia/autocomplete-preset-algolia@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.11.1.tgz#52780e7ccfe84c3b0c6fa403e3555b289ed589e5"
+ integrity sha512-iso7s41eeywyIwzC7cBMrK0kbWd3J/lKyZceaH0KteWyqoQAeNgNgAfbQsdp2m+bXFglOH4Hklr/0Y5SO8HTlg==
+ dependencies:
+ "@algolia/autocomplete-shared" "1.11.1"
+
+"@algolia/autocomplete-preset-algolia@1.9.3":
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz#64cca4a4304cfcad2cf730e83067e0c1b2f485da"
+ integrity sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==
+ dependencies:
+ "@algolia/autocomplete-shared" "1.9.3"
+
+"@algolia/autocomplete-shared@1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.11.1.tgz#ce5efbc94376954ea78848b0d45b3254e91ce259"
+ integrity sha512-bbX7dk41aAy7jlgaJTH/Suv7moGvmkudrrF2ECuMQUrWvl/xGfrj9ZYpLcMsT7TcTYf5SPtK5awXJnpQ4PTKEg==
+
+"@algolia/autocomplete-shared@1.9.3":
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz#2e22e830d36f0a9cf2c0ccd3c7f6d59435b77dfa"
+ integrity sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==
+
+"@algolia/autocomplete-theme-classic@^1.8.2":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@algolia/autocomplete-theme-classic/-/autocomplete-theme-classic-1.11.1.tgz#78895aa6551cc4d02df3b4f68899b41fb941027f"
+ integrity sha512-AsKpXXpxIjxOjPNuxWNI7gcbxebxkb18AV36qH6CO6LSAkxZ7SFwEcHwtOlCtk0lGfWZxKWJwI4jiclucBYYIA==
+
+"@algolia/cache-browser-local-storage@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.20.0.tgz#357318242fc542ffce41d6eb5b4a9b402921b0bb"
+ integrity sha512-uujahcBt4DxduBTvYdwO3sBfHuJvJokiC3BP1+O70fglmE1ShkH8lpXqZBac1rrU3FnNYSUs4pL9lBdTKeRPOQ==
+ dependencies:
+ "@algolia/cache-common" "4.20.0"
+
+"@algolia/cache-common@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.20.0.tgz#ec52230509fce891091ffd0d890618bcdc2fa20d"
+ integrity sha512-vCfxauaZutL3NImzB2G9LjLt36vKAckc6DhMp05An14kVo8F1Yofb6SIl6U3SaEz8pG2QOB9ptwM5c+zGevwIQ==
+
+"@algolia/cache-in-memory@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.20.0.tgz#5f18d057bd6b3b075022df085c4f83bcca4e3e67"
+ integrity sha512-Wm9ak/IaacAZXS4mB3+qF/KCoVSBV6aLgIGFEtQtJwjv64g4ePMapORGmCyulCFwfePaRAtcaTbMcJF+voc/bg==
+ dependencies:
+ "@algolia/cache-common" "4.20.0"
+
+"@algolia/client-account@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.20.0.tgz#23ce0b4cffd63100fb7c1aa1c67a4494de5bd645"
+ integrity sha512-GGToLQvrwo7am4zVkZTnKa72pheQeez/16sURDWm7Seyz+HUxKi3BM6fthVVPUEBhtJ0reyVtuK9ArmnaKl10Q==
+ dependencies:
+ "@algolia/client-common" "4.20.0"
+ "@algolia/client-search" "4.20.0"
+ "@algolia/transporter" "4.20.0"
+
+"@algolia/client-analytics@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.20.0.tgz#0aa6bef35d3a41ac3991b3f46fcd0bf00d276fa9"
+ integrity sha512-EIr+PdFMOallRdBTHHdKI3CstslgLORQG7844Mq84ib5oVFRVASuuPmG4bXBgiDbcsMLUeOC6zRVJhv1KWI0ug==
+ dependencies:
+ "@algolia/client-common" "4.20.0"
+ "@algolia/client-search" "4.20.0"
+ "@algolia/requester-common" "4.20.0"
+ "@algolia/transporter" "4.20.0"
+
+"@algolia/client-common@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.20.0.tgz#ca60f04466515548651c4371a742fbb8971790ef"
+ integrity sha512-P3WgMdEss915p+knMMSd/fwiHRHKvDu4DYRrCRaBrsfFw7EQHon+EbRSm4QisS9NYdxbS04kcvNoavVGthyfqQ==
+ dependencies:
+ "@algolia/requester-common" "4.20.0"
+ "@algolia/transporter" "4.20.0"
+
+"@algolia/client-personalization@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.20.0.tgz#ca81308e8ad0db3b27458b78355f124f29657181"
+ integrity sha512-N9+zx0tWOQsLc3K4PVRDV8GUeOLAY0i445En79Pr3zWB+m67V+n/8w4Kw1C5LlbHDDJcyhMMIlqezh6BEk7xAQ==
+ dependencies:
+ "@algolia/client-common" "4.20.0"
+ "@algolia/requester-common" "4.20.0"
+ "@algolia/transporter" "4.20.0"
+
+"@algolia/client-search@4.20.0", "@algolia/client-search@^4.12.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.20.0.tgz#3bcce817ca6caedc835e0eaf6f580e02ee7c3e15"
+ integrity sha512-zgwqnMvhWLdpzKTpd3sGmMlr4c+iS7eyyLGiaO51zDZWGMkpgoNVmltkzdBwxOVXz0RsFMznIxB9zuarUv4TZg==
+ dependencies:
+ "@algolia/client-common" "4.20.0"
+ "@algolia/requester-common" "4.20.0"
+ "@algolia/transporter" "4.20.0"
+
+"@algolia/events@^4.0.1":
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950"
+ integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==
+
+"@algolia/logger-common@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.20.0.tgz#f148ddf67e5d733a06213bebf7117cb8a651ab36"
+ integrity sha512-xouigCMB5WJYEwvoWW5XDv7Z9f0A8VoXJc3VKwlHJw/je+3p2RcDXfksLI4G4lIVncFUYMZx30tP/rsdlvvzHQ==
+
+"@algolia/logger-console@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.20.0.tgz#ac443d27c4e94357f3063e675039cef0aa2de0a7"
+ integrity sha512-THlIGG1g/FS63z0StQqDhT6bprUczBI8wnLT3JWvfAQDZX5P6fCg7dG+pIrUBpDIHGszgkqYEqECaKKsdNKOUA==
+ dependencies:
+ "@algolia/logger-common" "4.20.0"
+
+"@algolia/requester-browser-xhr@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.20.0.tgz#db16d0bdef018b93b51681d3f1e134aca4f64814"
+ integrity sha512-HbzoSjcjuUmYOkcHECkVTwAelmvTlgs48N6Owt4FnTOQdwn0b8pdht9eMgishvk8+F8bal354nhx/xOoTfwiAw==
+ dependencies:
+ "@algolia/requester-common" "4.20.0"
+
+"@algolia/requester-common@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.20.0.tgz#65694b2263a8712b4360fef18680528ffd435b5c"
+ integrity sha512-9h6ye6RY/BkfmeJp7Z8gyyeMrmmWsMOCRBXQDs4mZKKsyVlfIVICpcSibbeYcuUdurLhIlrOUkH3rQEgZzonng==
+
+"@algolia/requester-node-http@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.20.0.tgz#b52b182b52b0b16dec4070832267d484a6b1d5bb"
+ integrity sha512-ocJ66L60ABSSTRFnCHIEZpNHv6qTxsBwJEPfYaSBsLQodm0F9ptvalFkHMpvj5DfE22oZrcrLbOYM2bdPJRHng==
+ dependencies:
+ "@algolia/requester-common" "4.20.0"
+
+"@algolia/transporter@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.20.0.tgz#7e5b24333d7cc9a926b2f6a249f87c2889b944a9"
+ integrity sha512-Lsii1pGWOAISbzeyuf+r/GPhvHMPHSPrTDWNcIzOE1SG1inlJHICaVe2ikuoRjcpgxZNU54Jl+if15SUCsaTUg==
+ dependencies:
+ "@algolia/cache-common" "4.20.0"
+ "@algolia/logger-common" "4.20.0"
+ "@algolia/requester-common" "4.20.0"
+
+"@ampproject/remapping@^2.2.0":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
+ integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.8.3":
+ version "7.22.13"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
+ integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
+ dependencies:
+ "@babel/highlight" "^7.22.13"
+ chalk "^2.4.2"
+
+"@babel/compat-data@^7.22.20", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0"
+ integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==
+
+"@babel/core@7.12.9":
+ version "7.12.9"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.9.tgz#fd450c4ec10cdbb980e2928b7aa7a28484593fc8"
+ integrity sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ "@babel/generator" "^7.12.5"
+ "@babel/helper-module-transforms" "^7.12.1"
+ "@babel/helpers" "^7.12.5"
+ "@babel/parser" "^7.12.7"
+ "@babel/template" "^7.12.7"
+ "@babel/traverse" "^7.12.9"
+ "@babel/types" "^7.12.7"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.1"
+ json5 "^2.1.2"
+ lodash "^4.17.19"
+ resolve "^1.3.2"
+ semver "^5.4.1"
+ source-map "^0.5.0"
+
+"@babel/core@^7.18.6", "@babel/core@^7.19.6":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.0.tgz#f8259ae0e52a123eb40f552551e647b506a94d83"
+ integrity sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==
+ dependencies:
+ "@ampproject/remapping" "^2.2.0"
+ "@babel/code-frame" "^7.22.13"
+ "@babel/generator" "^7.23.0"
+ "@babel/helper-compilation-targets" "^7.22.15"
+ "@babel/helper-module-transforms" "^7.23.0"
+ "@babel/helpers" "^7.23.0"
+ "@babel/parser" "^7.23.0"
+ "@babel/template" "^7.22.15"
+ "@babel/traverse" "^7.23.0"
+ "@babel/types" "^7.23.0"
+ convert-source-map "^2.0.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.3"
+ semver "^6.3.1"
+
+"@babel/generator@^7.12.5", "@babel/generator@^7.18.7", "@babel/generator@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
+ integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
+ dependencies:
+ "@babel/types" "^7.23.0"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jsesc "^2.5.1"
+
+"@babel/helper-annotate-as-pure@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882"
+ integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956"
+ integrity sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==
+ dependencies:
+ "@babel/types" "^7.22.15"
+
+"@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52"
+ integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==
+ dependencies:
+ "@babel/compat-data" "^7.22.9"
+ "@babel/helper-validator-option" "^7.22.15"
+ browserslist "^4.21.9"
+ lru-cache "^5.1.1"
+ semver "^6.3.1"
+
+"@babel/helper-create-class-features-plugin@^7.22.11", "@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.22.5":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz#97a61b385e57fe458496fad19f8e63b63c867de4"
+ integrity sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-function-name" "^7.22.5"
+ "@babel/helper-member-expression-to-functions" "^7.22.15"
+ "@babel/helper-optimise-call-expression" "^7.22.5"
+ "@babel/helper-replace-supers" "^7.22.9"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ semver "^6.3.1"
+
+"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1"
+ integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ regexpu-core "^5.3.1"
+ semver "^6.3.1"
+
+"@babel/helper-define-polyfill-provider@^0.4.2":
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz#82c825cadeeeee7aad237618ebbe8fa1710015d7"
+ integrity sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.22.6"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ debug "^4.1.1"
+ lodash.debounce "^4.0.8"
+ resolve "^1.14.2"
+
+"@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
+ integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
+
+"@babel/helper-function-name@^7.22.5", "@babel/helper-function-name@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
+ integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
+ dependencies:
+ "@babel/template" "^7.22.15"
+ "@babel/types" "^7.23.0"
+
+"@babel/helper-hoist-variables@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
+ integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-member-expression-to-functions@^7.22.15":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz#9263e88cc5e41d39ec18c9a3e0eced59a3e7d366"
+ integrity sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==
+ dependencies:
+ "@babel/types" "^7.23.0"
+
+"@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0"
+ integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==
+ dependencies:
+ "@babel/types" "^7.22.15"
+
+"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz#3ec246457f6c842c0aee62a01f60739906f7047e"
+ integrity sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.22.20"
+ "@babel/helper-module-imports" "^7.22.15"
+ "@babel/helper-simple-access" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ "@babel/helper-validator-identifier" "^7.22.20"
+
+"@babel/helper-optimise-call-expression@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e"
+ integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-plugin-utils@7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375"
+ integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==
+
+"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295"
+ integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==
+
+"@babel/helper-remap-async-to-generator@^7.22.5", "@babel/helper-remap-async-to-generator@^7.22.9":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0"
+ integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-environment-visitor" "^7.22.20"
+ "@babel/helper-wrap-function" "^7.22.20"
+
+"@babel/helper-replace-supers@^7.22.5", "@babel/helper-replace-supers@^7.22.9":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793"
+ integrity sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.22.20"
+ "@babel/helper-member-expression-to-functions" "^7.22.15"
+ "@babel/helper-optimise-call-expression" "^7.22.5"
+
+"@babel/helper-simple-access@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de"
+ integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-skip-transparent-expression-wrappers@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847"
+ integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-split-export-declaration@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
+ integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-string-parser@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
+ integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
+
+"@babel/helper-validator-identifier@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
+ integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
+
+"@babel/helper-validator-option@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040"
+ integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==
+
+"@babel/helper-wrap-function@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569"
+ integrity sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==
+ dependencies:
+ "@babel/helper-function-name" "^7.22.5"
+ "@babel/template" "^7.22.15"
+ "@babel/types" "^7.22.19"
+
+"@babel/helpers@^7.12.5", "@babel/helpers@^7.23.0":
+ version "7.23.1"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.1.tgz#44e981e8ce2b9e99f8f0b703f3326a4636c16d15"
+ integrity sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==
+ dependencies:
+ "@babel/template" "^7.22.15"
+ "@babel/traverse" "^7.23.0"
+ "@babel/types" "^7.23.0"
+
+"@babel/highlight@^7.22.13":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
+ integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.22.20"
+ chalk "^2.4.2"
+ js-tokens "^4.0.0"
+
+"@babel/parser@^7.12.7", "@babel/parser@^7.18.8", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
+ integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
+
+"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz#02dc8a03f613ed5fdc29fb2f728397c78146c962"
+ integrity sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz#2aeb91d337d4e1a1e7ce85b76a37f5301781200f"
+ integrity sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+ "@babel/plugin-transform-optional-chaining" "^7.22.15"
+
+"@babel/plugin-proposal-object-rest-spread@7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz#def9bd03cea0f9b72283dac0ec22d289c7691069"
+ integrity sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
+ "@babel/plugin-transform-parameters" "^7.12.1"
+
+"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2":
+ version "7.21.0-placeholder-for-preset-env.2"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703"
+ integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==
+
+"@babel/plugin-syntax-async-generators@^7.8.4":
+ version "7.8.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
+ integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-class-properties@^7.12.13":
+ version "7.12.13"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10"
+ integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.12.13"
+
+"@babel/plugin-syntax-class-static-block@^7.14.5":
+ version "7.14.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406"
+ integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.14.5"
+
+"@babel/plugin-syntax-dynamic-import@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3"
+ integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-export-namespace-from@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a"
+ integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.3"
+
+"@babel/plugin-syntax-import-assertions@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz#07d252e2aa0bc6125567f742cd58619cb14dce98"
+ integrity sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-syntax-import-attributes@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz#ab840248d834410b829f569f5262b9e517555ecb"
+ integrity sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-syntax-import-meta@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51"
+ integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-json-strings@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a"
+ integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-jsx@7.12.1":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz#9d9d357cc818aa7ae7935917c1257f67677a0926"
+ integrity sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-jsx@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918"
+ integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
+ integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9"
+ integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-numeric-separator@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97"
+ integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-object-rest-spread@7.8.3", "@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
+ integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-optional-catch-binding@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1"
+ integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-optional-chaining@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
+ integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-private-property-in-object@^7.14.5":
+ version "7.14.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad"
+ integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.14.5"
+
+"@babel/plugin-syntax-top-level-await@^7.14.5":
+ version "7.14.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c"
+ integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.14.5"
+
+"@babel/plugin-syntax-typescript@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272"
+ integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-syntax-unicode-sets-regex@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357"
+ integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-arrow-functions@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz#e5ba566d0c58a5b2ba2a8b795450641950b71958"
+ integrity sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-async-generator-functions@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.15.tgz#3b153af4a6b779f340d5b80d3f634f55820aefa3"
+ integrity sha512-jBm1Es25Y+tVoTi5rfd5t1KLmL8ogLKpXszboWOTTtGFGz2RKnQe2yn7HbZ+kb/B8N0FVSGQo874NSlOU1T4+w==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-remap-async-to-generator" "^7.22.9"
+ "@babel/plugin-syntax-async-generators" "^7.8.4"
+
+"@babel/plugin-transform-async-to-generator@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz#c7a85f44e46f8952f6d27fe57c2ed3cc084c3775"
+ integrity sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==
+ dependencies:
+ "@babel/helper-module-imports" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-remap-async-to-generator" "^7.22.5"
+
+"@babel/plugin-transform-block-scoped-functions@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024"
+ integrity sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-block-scoping@^7.22.15":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz#8744d02c6c264d82e1a4bc5d2d501fd8aff6f022"
+ integrity sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-class-properties@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz#97a56e31ad8c9dc06a0b3710ce7803d5a48cca77"
+ integrity sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-class-static-block@^7.22.11":
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz#dc8cc6e498f55692ac6b4b89e56d87cec766c974"
+ integrity sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.22.11"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-class-static-block" "^7.14.5"
+
+"@babel/plugin-transform-classes@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz#aaf4753aee262a232bbc95451b4bdf9599c65a0b"
+ integrity sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-compilation-targets" "^7.22.15"
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-function-name" "^7.22.5"
+ "@babel/helper-optimise-call-expression" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-replace-supers" "^7.22.9"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ globals "^11.1.0"
+
+"@babel/plugin-transform-computed-properties@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz#cd1e994bf9f316bd1c2dafcd02063ec261bb3869"
+ integrity sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/template" "^7.22.5"
+
+"@babel/plugin-transform-destructuring@^7.22.15":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz#6447aa686be48b32eaf65a73e0e2c0bd010a266c"
+ integrity sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-dotall-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz#dbb4f0e45766eb544e193fb00e65a1dd3b2a4165"
+ integrity sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-duplicate-keys@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz#b6e6428d9416f5f0bba19c70d1e6e7e0b88ab285"
+ integrity sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-dynamic-import@^7.22.11":
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz#2c7722d2a5c01839eaf31518c6ff96d408e447aa"
+ integrity sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+
+"@babel/plugin-transform-exponentiation-operator@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz#402432ad544a1f9a480da865fda26be653e48f6a"
+ integrity sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==
+ dependencies:
+ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-export-namespace-from@^7.22.11":
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz#b3c84c8f19880b6c7440108f8929caf6056db26c"
+ integrity sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+
+"@babel/plugin-transform-for-of@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz#f64b4ccc3a4f131a996388fae7680b472b306b29"
+ integrity sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-function-name@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz#935189af68b01898e0d6d99658db6b164205c143"
+ integrity sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.22.5"
+ "@babel/helper-function-name" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-json-strings@^7.22.11":
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz#689a34e1eed1928a40954e37f74509f48af67835"
+ integrity sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-json-strings" "^7.8.3"
+
+"@babel/plugin-transform-literals@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz#e9341f4b5a167952576e23db8d435849b1dd7920"
+ integrity sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-logical-assignment-operators@^7.22.11":
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz#24c522a61688bde045b7d9bc3c2597a4d948fc9c"
+ integrity sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+
+"@babel/plugin-transform-member-expression-literals@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def"
+ integrity sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-modules-amd@^7.22.5":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz#05b2bc43373faa6d30ca89214731f76f966f3b88"
+ integrity sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.23.0"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-modules-commonjs@^7.22.15", "@babel/plugin-transform-modules-commonjs@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz#b3dba4757133b2762c00f4f94590cf6d52602481"
+ integrity sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.23.0"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-simple-access" "^7.22.5"
+
+"@babel/plugin-transform-modules-systemjs@^7.22.11":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz#77591e126f3ff4132a40595a6cccd00a6b60d160"
+ integrity sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==
+ dependencies:
+ "@babel/helper-hoist-variables" "^7.22.5"
+ "@babel/helper-module-transforms" "^7.23.0"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-validator-identifier" "^7.22.20"
+
+"@babel/plugin-transform-modules-umd@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz#4694ae40a87b1745e3775b6a7fe96400315d4f98"
+ integrity sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-named-capturing-groups-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f"
+ integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-new-target@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz#1b248acea54ce44ea06dfd37247ba089fcf9758d"
+ integrity sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-nullish-coalescing-operator@^7.22.11":
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz#debef6c8ba795f5ac67cd861a81b744c5d38d9fc"
+ integrity sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+
+"@babel/plugin-transform-numeric-separator@^7.22.11":
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz#498d77dc45a6c6db74bb829c02a01c1d719cbfbd"
+ integrity sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+
+"@babel/plugin-transform-object-rest-spread@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz#21a95db166be59b91cde48775310c0df6e1da56f"
+ integrity sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==
+ dependencies:
+ "@babel/compat-data" "^7.22.9"
+ "@babel/helper-compilation-targets" "^7.22.15"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+ "@babel/plugin-transform-parameters" "^7.22.15"
+
+"@babel/plugin-transform-object-super@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c"
+ integrity sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-replace-supers" "^7.22.5"
+
+"@babel/plugin-transform-optional-catch-binding@^7.22.11":
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz#461cc4f578a127bb055527b3e77404cad38c08e0"
+ integrity sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+
+"@babel/plugin-transform-optional-chaining@^7.22.15":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz#73ff5fc1cf98f542f09f29c0631647d8ad0be158"
+ integrity sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+
+"@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz#719ca82a01d177af358df64a514d64c2e3edb114"
+ integrity sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-private-methods@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz#21c8af791f76674420a147ae62e9935d790f8722"
+ integrity sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-private-property-in-object@^7.22.11":
+ version "7.22.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz#ad45c4fc440e9cb84c718ed0906d96cf40f9a4e1"
+ integrity sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-create-class-features-plugin" "^7.22.11"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
+
+"@babel/plugin-transform-property-literals@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766"
+ integrity sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-react-constant-elements@^7.18.12":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.22.5.tgz#6dfa7c1c37f7d7279e417ceddf5a04abb8bb9c29"
+ integrity sha512-BF5SXoO+nX3h5OhlN78XbbDrBOffv+AxPP2ENaJOVqjWCgBDeOY3WcaUcddutGSfoap+5NEQ/q/4I3WZIvgkXA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-react-display-name@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz#3c4326f9fce31c7968d6cb9debcaf32d9e279a2b"
+ integrity sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-react-jsx-development@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz#e716b6edbef972a92165cd69d92f1255f7e73e87"
+ integrity sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==
+ dependencies:
+ "@babel/plugin-transform-react-jsx" "^7.22.5"
+
+"@babel/plugin-transform-react-jsx@^7.22.15", "@babel/plugin-transform-react-jsx@^7.22.5":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz#7e6266d88705d7c49f11c98db8b9464531289cd6"
+ integrity sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-module-imports" "^7.22.15"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-jsx" "^7.22.5"
+ "@babel/types" "^7.22.15"
+
+"@babel/plugin-transform-react-pure-annotations@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz#1f58363eef6626d6fa517b95ac66fe94685e32c0"
+ integrity sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-regenerator@^7.22.10":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz#8ceef3bd7375c4db7652878b0241b2be5d0c3cca"
+ integrity sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ regenerator-transform "^0.15.2"
+
+"@babel/plugin-transform-reserved-words@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz#832cd35b81c287c4bcd09ce03e22199641f964fb"
+ integrity sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-runtime@^7.18.6":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.15.tgz#3a625c4c05a39e932d7d34f5d4895cdd0172fdc9"
+ integrity sha512-tEVLhk8NRZSmwQ0DJtxxhTrCht1HVo8VaMzYT4w6lwyKBuHsgoioAUA7/6eT2fRfc5/23fuGdlwIxXhRVgWr4g==
+ dependencies:
+ "@babel/helper-module-imports" "^7.22.15"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ babel-plugin-polyfill-corejs2 "^0.4.5"
+ babel-plugin-polyfill-corejs3 "^0.8.3"
+ babel-plugin-polyfill-regenerator "^0.5.2"
+ semver "^6.3.1"
+
+"@babel/plugin-transform-shorthand-properties@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz#6e277654be82b5559fc4b9f58088507c24f0c624"
+ integrity sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-spread@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz#6487fd29f229c95e284ba6c98d65eafb893fea6b"
+ integrity sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+
+"@babel/plugin-transform-sticky-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz#295aba1595bfc8197abd02eae5fc288c0deb26aa"
+ integrity sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-template-literals@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff"
+ integrity sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-typeof-symbol@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz#5e2ba478da4b603af8673ff7c54f75a97b716b34"
+ integrity sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-typescript@^7.22.15":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.15.tgz#15adef906451d86349eb4b8764865c960eb54127"
+ integrity sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-create-class-features-plugin" "^7.22.15"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-typescript" "^7.22.5"
+
+"@babel/plugin-transform-unicode-escapes@^7.22.10":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz#c723f380f40a2b2f57a62df24c9005834c8616d9"
+ integrity sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-unicode-property-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz#098898f74d5c1e86660dc112057b2d11227f1c81"
+ integrity sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-unicode-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183"
+ integrity sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-unicode-sets-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz#77788060e511b708ffc7d42fdfbc5b37c3004e91"
+ integrity sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/preset-env@^7.18.6", "@babel/preset-env@^7.19.4":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.20.tgz#de9e9b57e1127ce0a2f580831717f7fb677ceedb"
+ integrity sha512-11MY04gGC4kSzlPHRfvVkNAZhUxOvm7DCJ37hPDnUENwe06npjIRAfInEMTGSb4LZK5ZgDFkv5hw0lGebHeTyg==
+ dependencies:
+ "@babel/compat-data" "^7.22.20"
+ "@babel/helper-compilation-targets" "^7.22.15"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-validator-option" "^7.22.15"
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.15"
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.15"
+ "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2"
+ "@babel/plugin-syntax-async-generators" "^7.8.4"
+ "@babel/plugin-syntax-class-properties" "^7.12.13"
+ "@babel/plugin-syntax-class-static-block" "^7.14.5"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+ "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+ "@babel/plugin-syntax-import-assertions" "^7.22.5"
+ "@babel/plugin-syntax-import-attributes" "^7.22.5"
+ "@babel/plugin-syntax-import-meta" "^7.10.4"
+ "@babel/plugin-syntax-json-strings" "^7.8.3"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+ "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
+ "@babel/plugin-syntax-top-level-await" "^7.14.5"
+ "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6"
+ "@babel/plugin-transform-arrow-functions" "^7.22.5"
+ "@babel/plugin-transform-async-generator-functions" "^7.22.15"
+ "@babel/plugin-transform-async-to-generator" "^7.22.5"
+ "@babel/plugin-transform-block-scoped-functions" "^7.22.5"
+ "@babel/plugin-transform-block-scoping" "^7.22.15"
+ "@babel/plugin-transform-class-properties" "^7.22.5"
+ "@babel/plugin-transform-class-static-block" "^7.22.11"
+ "@babel/plugin-transform-classes" "^7.22.15"
+ "@babel/plugin-transform-computed-properties" "^7.22.5"
+ "@babel/plugin-transform-destructuring" "^7.22.15"
+ "@babel/plugin-transform-dotall-regex" "^7.22.5"
+ "@babel/plugin-transform-duplicate-keys" "^7.22.5"
+ "@babel/plugin-transform-dynamic-import" "^7.22.11"
+ "@babel/plugin-transform-exponentiation-operator" "^7.22.5"
+ "@babel/plugin-transform-export-namespace-from" "^7.22.11"
+ "@babel/plugin-transform-for-of" "^7.22.15"
+ "@babel/plugin-transform-function-name" "^7.22.5"
+ "@babel/plugin-transform-json-strings" "^7.22.11"
+ "@babel/plugin-transform-literals" "^7.22.5"
+ "@babel/plugin-transform-logical-assignment-operators" "^7.22.11"
+ "@babel/plugin-transform-member-expression-literals" "^7.22.5"
+ "@babel/plugin-transform-modules-amd" "^7.22.5"
+ "@babel/plugin-transform-modules-commonjs" "^7.22.15"
+ "@babel/plugin-transform-modules-systemjs" "^7.22.11"
+ "@babel/plugin-transform-modules-umd" "^7.22.5"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5"
+ "@babel/plugin-transform-new-target" "^7.22.5"
+ "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.11"
+ "@babel/plugin-transform-numeric-separator" "^7.22.11"
+ "@babel/plugin-transform-object-rest-spread" "^7.22.15"
+ "@babel/plugin-transform-object-super" "^7.22.5"
+ "@babel/plugin-transform-optional-catch-binding" "^7.22.11"
+ "@babel/plugin-transform-optional-chaining" "^7.22.15"
+ "@babel/plugin-transform-parameters" "^7.22.15"
+ "@babel/plugin-transform-private-methods" "^7.22.5"
+ "@babel/plugin-transform-private-property-in-object" "^7.22.11"
+ "@babel/plugin-transform-property-literals" "^7.22.5"
+ "@babel/plugin-transform-regenerator" "^7.22.10"
+ "@babel/plugin-transform-reserved-words" "^7.22.5"
+ "@babel/plugin-transform-shorthand-properties" "^7.22.5"
+ "@babel/plugin-transform-spread" "^7.22.5"
+ "@babel/plugin-transform-sticky-regex" "^7.22.5"
+ "@babel/plugin-transform-template-literals" "^7.22.5"
+ "@babel/plugin-transform-typeof-symbol" "^7.22.5"
+ "@babel/plugin-transform-unicode-escapes" "^7.22.10"
+ "@babel/plugin-transform-unicode-property-regex" "^7.22.5"
+ "@babel/plugin-transform-unicode-regex" "^7.22.5"
+ "@babel/plugin-transform-unicode-sets-regex" "^7.22.5"
+ "@babel/preset-modules" "0.1.6-no-external-plugins"
+ "@babel/types" "^7.22.19"
+ babel-plugin-polyfill-corejs2 "^0.4.5"
+ babel-plugin-polyfill-corejs3 "^0.8.3"
+ babel-plugin-polyfill-regenerator "^0.5.2"
+ core-js-compat "^3.31.0"
+ semver "^6.3.1"
+
+"@babel/preset-modules@0.1.6-no-external-plugins":
+ version "0.1.6-no-external-plugins"
+ resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a"
+ integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/types" "^7.4.4"
+ esutils "^2.0.2"
+
+"@babel/preset-react@^7.18.6":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.22.15.tgz#9a776892b648e13cc8ca2edf5ed1264eea6b6afc"
+ integrity sha512-Csy1IJ2uEh/PecCBXXoZGAZBeCATTuePzCSB7dLYWS0vOEj6CNpjxIhW4duWwZodBNueH7QO14WbGn8YyeuN9w==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-validator-option" "^7.22.15"
+ "@babel/plugin-transform-react-display-name" "^7.22.5"
+ "@babel/plugin-transform-react-jsx" "^7.22.15"
+ "@babel/plugin-transform-react-jsx-development" "^7.22.5"
+ "@babel/plugin-transform-react-pure-annotations" "^7.22.5"
+
+"@babel/preset-typescript@^7.18.6":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.23.0.tgz#cc6602d13e7e5b2087c811912b87cf937a9129d9"
+ integrity sha512-6P6VVa/NM/VlAYj5s2Aq/gdVg8FSENCg3wlZ6Qau9AcPaoF5LbN1nyGlR9DTRIw9PpxI94e+ReydsJHcjwAweg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-validator-option" "^7.22.15"
+ "@babel/plugin-syntax-jsx" "^7.22.5"
+ "@babel/plugin-transform-modules-commonjs" "^7.23.0"
+ "@babel/plugin-transform-typescript" "^7.22.15"
+
+"@babel/regjsgen@^0.8.0":
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
+ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
+
+"@babel/runtime-corejs3@^7.18.6":
+ version "7.23.1"
+ resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.23.1.tgz#d03f5819f4ba81a21dd1f80edfb19983e9e20fc1"
+ integrity sha512-OKKfytwoc0tr7cDHwQm0RLVR3y+hDGFz3EPuvLNU/0fOeXJeKNIHj7ffNVFnncWt3sC58uyUCRSzf8nBQbyF6A==
+ dependencies:
+ core-js-pure "^3.30.2"
+ regenerator-runtime "^0.14.0"
+
+"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.8.4":
+ version "7.23.1"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.1.tgz#72741dc4d413338a91dcb044a86f3c0bc402646d"
+ integrity sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==
+ dependencies:
+ regenerator-runtime "^0.14.0"
+
+"@babel/template@^7.12.7", "@babel/template@^7.22.15", "@babel/template@^7.22.5":
+ version "7.22.15"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
+ integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
+ dependencies:
+ "@babel/code-frame" "^7.22.13"
+ "@babel/parser" "^7.22.15"
+ "@babel/types" "^7.22.15"
+
+"@babel/traverse@^7.12.9", "@babel/traverse@^7.18.8", "@babel/traverse@^7.23.0":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.0.tgz#18196ddfbcf4ccea324b7f6d3ada00d8c5a99c53"
+ integrity sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==
+ dependencies:
+ "@babel/code-frame" "^7.22.13"
+ "@babel/generator" "^7.23.0"
+ "@babel/helper-environment-visitor" "^7.22.20"
+ "@babel/helper-function-name" "^7.23.0"
+ "@babel/helper-hoist-variables" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ "@babel/parser" "^7.23.0"
+ "@babel/types" "^7.23.0"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
+"@babel/types@^7.12.7", "@babel/types@^7.20.0", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.4.4":
+ version "7.23.0"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
+ integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
+ dependencies:
+ "@babel/helper-string-parser" "^7.22.5"
+ "@babel/helper-validator-identifier" "^7.22.20"
+ to-fast-properties "^2.0.0"
+
+"@braintree/sanitize-url@^6.0.0":
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz#923ca57e173c6b232bbbb07347b1be982f03e783"
+ integrity sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==
+
+"@cmfcmf/docusaurus-search-local@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@cmfcmf/docusaurus-search-local/-/docusaurus-search-local-1.1.0.tgz#3db5d7d6e05985cc3b06cec10436385c53033c69"
+ integrity sha512-0IVb/aA0IK8ZlktuxmgXmluXfcSpo6Vdd2nG21y1aOH9nVYnPP231Dn0H8Ng9Qf9ronQQCDWHnuWpYOr9rUrEQ==
+ dependencies:
+ "@algolia/autocomplete-js" "^1.8.2"
+ "@algolia/autocomplete-theme-classic" "^1.8.2"
+ "@algolia/client-search" "^4.12.0"
+ algoliasearch "^4.12.0"
+ cheerio "^1.0.0-rc.9"
+ clsx "^1.1.1"
+ lunr-languages "^1.4.0"
+ mark.js "^8.11.1"
+
+"@colors/colors@1.5.0":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
+ integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
+
+"@discoveryjs/json-ext@0.5.7":
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
+ integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
+
+"@docsearch/css@3.5.2":
+ version "3.5.2"
+ resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.5.2.tgz#610f47b48814ca94041df969d9fcc47b91fc5aac"
+ integrity sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==
+
+"@docsearch/react@^3.1.1":
+ version "3.5.2"
+ resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.5.2.tgz#2e6bbee00eb67333b64906352734da6aef1232b9"
+ integrity sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==
+ dependencies:
+ "@algolia/autocomplete-core" "1.9.3"
+ "@algolia/autocomplete-preset-algolia" "1.9.3"
+ "@docsearch/css" "3.5.2"
+ algoliasearch "^4.19.1"
+
+"@docusaurus/core@2.4.3", "@docusaurus/core@^2.3.1":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.4.3.tgz#d86624901386fd8164ce4bff9cc7f16fde57f523"
+ integrity sha512-dWH5P7cgeNSIg9ufReX6gaCl/TmrGKD38Orbwuz05WPhAQtFXHd5B8Qym1TiXfvUNvwoYKkAJOJuGe8ou0Z7PA==
+ dependencies:
+ "@babel/core" "^7.18.6"
+ "@babel/generator" "^7.18.7"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+ "@babel/plugin-transform-runtime" "^7.18.6"
+ "@babel/preset-env" "^7.18.6"
+ "@babel/preset-react" "^7.18.6"
+ "@babel/preset-typescript" "^7.18.6"
+ "@babel/runtime" "^7.18.6"
+ "@babel/runtime-corejs3" "^7.18.6"
+ "@babel/traverse" "^7.18.8"
+ "@docusaurus/cssnano-preset" "2.4.3"
+ "@docusaurus/logger" "2.4.3"
+ "@docusaurus/mdx-loader" "2.4.3"
+ "@docusaurus/react-loadable" "5.5.2"
+ "@docusaurus/utils" "2.4.3"
+ "@docusaurus/utils-common" "2.4.3"
+ "@docusaurus/utils-validation" "2.4.3"
+ "@slorber/static-site-generator-webpack-plugin" "^4.0.7"
+ "@svgr/webpack" "^6.2.1"
+ autoprefixer "^10.4.7"
+ babel-loader "^8.2.5"
+ babel-plugin-dynamic-import-node "^2.3.3"
+ boxen "^6.2.1"
+ chalk "^4.1.2"
+ chokidar "^3.5.3"
+ clean-css "^5.3.0"
+ cli-table3 "^0.6.2"
+ combine-promises "^1.1.0"
+ commander "^5.1.0"
+ copy-webpack-plugin "^11.0.0"
+ core-js "^3.23.3"
+ css-loader "^6.7.1"
+ css-minimizer-webpack-plugin "^4.0.0"
+ cssnano "^5.1.12"
+ del "^6.1.1"
+ detect-port "^1.3.0"
+ escape-html "^1.0.3"
+ eta "^2.0.0"
+ file-loader "^6.2.0"
+ fs-extra "^10.1.0"
+ html-minifier-terser "^6.1.0"
+ html-tags "^3.2.0"
+ html-webpack-plugin "^5.5.0"
+ import-fresh "^3.3.0"
+ leven "^3.1.0"
+ lodash "^4.17.21"
+ mini-css-extract-plugin "^2.6.1"
+ postcss "^8.4.14"
+ postcss-loader "^7.0.0"
+ prompts "^2.4.2"
+ react-dev-utils "^12.0.1"
+ react-helmet-async "^1.3.0"
+ react-loadable "npm:@docusaurus/react-loadable@5.5.2"
+ react-loadable-ssr-addon-v5-slorber "^1.0.1"
+ react-router "^5.3.3"
+ react-router-config "^5.1.1"
+ react-router-dom "^5.3.3"
+ rtl-detect "^1.0.4"
+ semver "^7.3.7"
+ serve-handler "^6.1.3"
+ shelljs "^0.8.5"
+ terser-webpack-plugin "^5.3.3"
+ tslib "^2.4.0"
+ update-notifier "^5.1.0"
+ url-loader "^4.1.1"
+ wait-on "^6.0.1"
+ webpack "^5.73.0"
+ webpack-bundle-analyzer "^4.5.0"
+ webpack-dev-server "^4.9.3"
+ webpack-merge "^5.8.0"
+ webpackbar "^5.0.2"
+
+"@docusaurus/cssnano-preset@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.4.3.tgz#1d7e833c41ce240fcc2812a2ac27f7b862f32de0"
+ integrity sha512-ZvGSRCi7z9wLnZrXNPG6DmVPHdKGd8dIn9pYbEOFiYihfv4uDR3UtxogmKf+rT8ZlKFf5Lqne8E8nt08zNM8CA==
+ dependencies:
+ cssnano-preset-advanced "^5.3.8"
+ postcss "^8.4.14"
+ postcss-sort-media-queries "^4.2.1"
+ tslib "^2.4.0"
+
+"@docusaurus/logger@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.4.3.tgz#518bbc965fb4ebe8f1d0b14e5f4161607552d34c"
+ integrity sha512-Zxws7r3yLufk9xM1zq9ged0YHs65mlRmtsobnFkdZTxWXdTYlWWLWdKyNKAsVC+D7zg+pv2fGbyabdOnyZOM3w==
+ dependencies:
+ chalk "^4.1.2"
+ tslib "^2.4.0"
+
+"@docusaurus/mdx-loader@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.4.3.tgz#e8ff37f30a060eaa97b8121c135f74cb531a4a3e"
+ integrity sha512-b1+fDnWtl3GiqkL0BRjYtc94FZrcDDBV1j8446+4tptB9BAOlePwG2p/pK6vGvfL53lkOsszXMghr2g67M0vCw==
+ dependencies:
+ "@babel/parser" "^7.18.8"
+ "@babel/traverse" "^7.18.8"
+ "@docusaurus/logger" "2.4.3"
+ "@docusaurus/utils" "2.4.3"
+ "@mdx-js/mdx" "^1.6.22"
+ escape-html "^1.0.3"
+ file-loader "^6.2.0"
+ fs-extra "^10.1.0"
+ image-size "^1.0.1"
+ mdast-util-to-string "^2.0.0"
+ remark-emoji "^2.2.0"
+ stringify-object "^3.3.0"
+ tslib "^2.4.0"
+ unified "^9.2.2"
+ unist-util-visit "^2.0.3"
+ url-loader "^4.1.1"
+ webpack "^5.73.0"
+
+"@docusaurus/module-type-aliases@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-2.4.3.tgz#d08ef67e4151e02f352a2836bcf9ecde3b9c56ac"
+ integrity sha512-cwkBkt1UCiduuvEAo7XZY01dJfRn7UR/75mBgOdb1hKknhrabJZ8YH+7savd/y9kLExPyrhe0QwdS9GuzsRRIA==
+ dependencies:
+ "@docusaurus/react-loadable" "5.5.2"
+ "@docusaurus/types" "2.4.3"
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+ "@types/react-router-config" "*"
+ "@types/react-router-dom" "*"
+ react-helmet-async "*"
+ react-loadable "npm:@docusaurus/react-loadable@5.5.2"
+
+"@docusaurus/plugin-content-blog@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.4.3.tgz#6473b974acab98e967414d8bbb0d37e0cedcea14"
+ integrity sha512-PVhypqaA0t98zVDpOeTqWUTvRqCEjJubtfFUQ7zJNYdbYTbS/E/ytq6zbLVsN/dImvemtO/5JQgjLxsh8XLo8Q==
+ dependencies:
+ "@docusaurus/core" "2.4.3"
+ "@docusaurus/logger" "2.4.3"
+ "@docusaurus/mdx-loader" "2.4.3"
+ "@docusaurus/types" "2.4.3"
+ "@docusaurus/utils" "2.4.3"
+ "@docusaurus/utils-common" "2.4.3"
+ "@docusaurus/utils-validation" "2.4.3"
+ cheerio "^1.0.0-rc.12"
+ feed "^4.2.2"
+ fs-extra "^10.1.0"
+ lodash "^4.17.21"
+ reading-time "^1.5.0"
+ tslib "^2.4.0"
+ unist-util-visit "^2.0.3"
+ utility-types "^3.10.0"
+ webpack "^5.73.0"
+
+"@docusaurus/plugin-content-docs@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.4.3.tgz#aa224c0512351e81807adf778ca59fd9cd136973"
+ integrity sha512-N7Po2LSH6UejQhzTCsvuX5NOzlC+HiXOVvofnEPj0WhMu1etpLEXE6a4aTxrtg95lQ5kf0xUIdjX9sh3d3G76A==
+ dependencies:
+ "@docusaurus/core" "2.4.3"
+ "@docusaurus/logger" "2.4.3"
+ "@docusaurus/mdx-loader" "2.4.3"
+ "@docusaurus/module-type-aliases" "2.4.3"
+ "@docusaurus/types" "2.4.3"
+ "@docusaurus/utils" "2.4.3"
+ "@docusaurus/utils-validation" "2.4.3"
+ "@types/react-router-config" "^5.0.6"
+ combine-promises "^1.1.0"
+ fs-extra "^10.1.0"
+ import-fresh "^3.3.0"
+ js-yaml "^4.1.0"
+ lodash "^4.17.21"
+ tslib "^2.4.0"
+ utility-types "^3.10.0"
+ webpack "^5.73.0"
+
+"@docusaurus/plugin-content-pages@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.4.3.tgz#7f285e718b53da8c8d0101e70840c75b9c0a1ac0"
+ integrity sha512-txtDVz7y3zGk67q0HjG0gRttVPodkHqE0bpJ+7dOaTH40CQFLSh7+aBeGnPOTl+oCPG+hxkim4SndqPqXjQ8Bg==
+ dependencies:
+ "@docusaurus/core" "2.4.3"
+ "@docusaurus/mdx-loader" "2.4.3"
+ "@docusaurus/types" "2.4.3"
+ "@docusaurus/utils" "2.4.3"
+ "@docusaurus/utils-validation" "2.4.3"
+ fs-extra "^10.1.0"
+ tslib "^2.4.0"
+ webpack "^5.73.0"
+
+"@docusaurus/plugin-debug@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.4.3.tgz#2f90eb0c9286a9f225444e3a88315676fe02c245"
+ integrity sha512-LkUbuq3zCmINlFb+gAd4ZvYr+bPAzMC0hwND4F7V9bZ852dCX8YoWyovVUBKq4er1XsOwSQaHmNGtObtn8Av8Q==
+ dependencies:
+ "@docusaurus/core" "2.4.3"
+ "@docusaurus/types" "2.4.3"
+ "@docusaurus/utils" "2.4.3"
+ fs-extra "^10.1.0"
+ react-json-view "^1.21.3"
+ tslib "^2.4.0"
+
+"@docusaurus/plugin-google-analytics@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.4.3.tgz#0d19993136ade6f7a7741251b4f617400d92ab45"
+ integrity sha512-KzBV3k8lDkWOhg/oYGxlK5o9bOwX7KpPc/FTWoB+SfKhlHfhq7qcQdMi1elAaVEIop8tgK6gD1E58Q+XC6otSQ==
+ dependencies:
+ "@docusaurus/core" "2.4.3"
+ "@docusaurus/types" "2.4.3"
+ "@docusaurus/utils-validation" "2.4.3"
+ tslib "^2.4.0"
+
+"@docusaurus/plugin-google-gtag@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.4.3.tgz#e1a80b0696771b488562e5b60eff21c9932d9e1c"
+ integrity sha512-5FMg0rT7sDy4i9AGsvJC71MQrqQZwgLNdDetLEGDHLfSHLvJhQbTCUGbGXknUgWXQJckcV/AILYeJy+HhxeIFA==
+ dependencies:
+ "@docusaurus/core" "2.4.3"
+ "@docusaurus/types" "2.4.3"
+ "@docusaurus/utils-validation" "2.4.3"
+ tslib "^2.4.0"
+
+"@docusaurus/plugin-google-tag-manager@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-2.4.3.tgz#e41fbf79b0ffc2de1cc4013eb77798cff0ad98e3"
+ integrity sha512-1jTzp71yDGuQiX9Bi0pVp3alArV0LSnHXempvQTxwCGAEzUWWaBg4d8pocAlTpbP9aULQQqhgzrs8hgTRPOM0A==
+ dependencies:
+ "@docusaurus/core" "2.4.3"
+ "@docusaurus/types" "2.4.3"
+ "@docusaurus/utils-validation" "2.4.3"
+ tslib "^2.4.0"
+
+"@docusaurus/plugin-sitemap@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.4.3.tgz#1b3930900a8f89670ce7e8f83fb4730cd3298c32"
+ integrity sha512-LRQYrK1oH1rNfr4YvWBmRzTL0LN9UAPxBbghgeFRBm5yloF6P+zv1tm2pe2hQTX/QP5bSKdnajCvfnScgKXMZQ==
+ dependencies:
+ "@docusaurus/core" "2.4.3"
+ "@docusaurus/logger" "2.4.3"
+ "@docusaurus/types" "2.4.3"
+ "@docusaurus/utils" "2.4.3"
+ "@docusaurus/utils-common" "2.4.3"
+ "@docusaurus/utils-validation" "2.4.3"
+ fs-extra "^10.1.0"
+ sitemap "^7.1.1"
+ tslib "^2.4.0"
+
+"@docusaurus/preset-classic@^2.3.1":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.4.3.tgz#074c57ebf29fa43d23bd1c8ce691226f542bc262"
+ integrity sha512-tRyMliepY11Ym6hB1rAFSNGwQDpmszvWYJvlK1E+md4SW8i6ylNHtpZjaYFff9Mdk3i/Pg8ItQq9P0daOJAvQw==
+ dependencies:
+ "@docusaurus/core" "2.4.3"
+ "@docusaurus/plugin-content-blog" "2.4.3"
+ "@docusaurus/plugin-content-docs" "2.4.3"
+ "@docusaurus/plugin-content-pages" "2.4.3"
+ "@docusaurus/plugin-debug" "2.4.3"
+ "@docusaurus/plugin-google-analytics" "2.4.3"
+ "@docusaurus/plugin-google-gtag" "2.4.3"
+ "@docusaurus/plugin-google-tag-manager" "2.4.3"
+ "@docusaurus/plugin-sitemap" "2.4.3"
+ "@docusaurus/theme-classic" "2.4.3"
+ "@docusaurus/theme-common" "2.4.3"
+ "@docusaurus/theme-search-algolia" "2.4.3"
+ "@docusaurus/types" "2.4.3"
+
+"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2":
+ version "5.5.2"
+ resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce"
+ integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
+ dependencies:
+ "@types/react" "*"
+ prop-types "^15.6.2"
+
+"@docusaurus/theme-classic@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.4.3.tgz#29360f2eb03a0e1686eb19668633ef313970ee8f"
+ integrity sha512-QKRAJPSGPfDY2yCiPMIVyr+MqwZCIV2lxNzqbyUW0YkrlmdzzP3WuQJPMGLCjWgQp/5c9kpWMvMxjhpZx1R32Q==
+ dependencies:
+ "@docusaurus/core" "2.4.3"
+ "@docusaurus/mdx-loader" "2.4.3"
+ "@docusaurus/module-type-aliases" "2.4.3"
+ "@docusaurus/plugin-content-blog" "2.4.3"
+ "@docusaurus/plugin-content-docs" "2.4.3"
+ "@docusaurus/plugin-content-pages" "2.4.3"
+ "@docusaurus/theme-common" "2.4.3"
+ "@docusaurus/theme-translations" "2.4.3"
+ "@docusaurus/types" "2.4.3"
+ "@docusaurus/utils" "2.4.3"
+ "@docusaurus/utils-common" "2.4.3"
+ "@docusaurus/utils-validation" "2.4.3"
+ "@mdx-js/react" "^1.6.22"
+ clsx "^1.2.1"
+ copy-text-to-clipboard "^3.0.1"
+ infima "0.2.0-alpha.43"
+ lodash "^4.17.21"
+ nprogress "^0.2.0"
+ postcss "^8.4.14"
+ prism-react-renderer "^1.3.5"
+ prismjs "^1.28.0"
+ react-router-dom "^5.3.3"
+ rtlcss "^3.5.0"
+ tslib "^2.4.0"
+ utility-types "^3.10.0"
+
+"@docusaurus/theme-common@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.4.3.tgz#bb31d70b6b67d0bdef9baa343192dcec49946a2e"
+ integrity sha512-7KaDJBXKBVGXw5WOVt84FtN8czGWhM0lbyWEZXGp8AFfL6sZQfRTluFp4QriR97qwzSyOfQb+nzcDZZU4tezUw==
+ dependencies:
+ "@docusaurus/mdx-loader" "2.4.3"
+ "@docusaurus/module-type-aliases" "2.4.3"
+ "@docusaurus/plugin-content-blog" "2.4.3"
+ "@docusaurus/plugin-content-docs" "2.4.3"
+ "@docusaurus/plugin-content-pages" "2.4.3"
+ "@docusaurus/utils" "2.4.3"
+ "@docusaurus/utils-common" "2.4.3"
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+ "@types/react-router-config" "*"
+ clsx "^1.2.1"
+ parse-numeric-range "^1.3.0"
+ prism-react-renderer "^1.3.5"
+ tslib "^2.4.0"
+ use-sync-external-store "^1.2.0"
+ utility-types "^3.10.0"
+
+"@docusaurus/theme-mermaid@^2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/theme-mermaid/-/theme-mermaid-2.4.3.tgz#b40194fb4f46813a18d1350a188d43b68a8192dd"
+ integrity sha512-S1tZ3xpowtFiTrpTKmvVbRHUYGOlEG5CnPzWlO4huJT1sAwLR+pD6f9DYUlPv2+9NezF3EfUrUyW9xLH0UP58w==
+ dependencies:
+ "@docusaurus/core" "2.4.3"
+ "@docusaurus/module-type-aliases" "2.4.3"
+ "@docusaurus/theme-common" "2.4.3"
+ "@docusaurus/types" "2.4.3"
+ "@docusaurus/utils-validation" "2.4.3"
+ "@mdx-js/react" "^1.6.22"
+ mermaid "^9.2.2"
+ tslib "^2.4.0"
+
+"@docusaurus/theme-search-algolia@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.4.3.tgz#32d4cbefc3deba4112068fbdb0bde11ac51ece53"
+ integrity sha512-jziq4f6YVUB5hZOB85ELATwnxBz/RmSLD3ksGQOLDPKVzat4pmI8tddNWtriPpxR04BNT+ZfpPUMFkNFetSW1Q==
+ dependencies:
+ "@docsearch/react" "^3.1.1"
+ "@docusaurus/core" "2.4.3"
+ "@docusaurus/logger" "2.4.3"
+ "@docusaurus/plugin-content-docs" "2.4.3"
+ "@docusaurus/theme-common" "2.4.3"
+ "@docusaurus/theme-translations" "2.4.3"
+ "@docusaurus/utils" "2.4.3"
+ "@docusaurus/utils-validation" "2.4.3"
+ algoliasearch "^4.13.1"
+ algoliasearch-helper "^3.10.0"
+ clsx "^1.2.1"
+ eta "^2.0.0"
+ fs-extra "^10.1.0"
+ lodash "^4.17.21"
+ tslib "^2.4.0"
+ utility-types "^3.10.0"
+
+"@docusaurus/theme-translations@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.4.3.tgz#91ac73fc49b8c652b7a54e88b679af57d6ac6102"
+ integrity sha512-H4D+lbZbjbKNS/Zw1Lel64PioUAIT3cLYYJLUf3KkuO/oc9e0QCVhIYVtUI2SfBCF2NNdlyhBDQEEMygsCedIg==
+ dependencies:
+ fs-extra "^10.1.0"
+ tslib "^2.4.0"
+
+"@docusaurus/types@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.4.3.tgz#4aead281ca09f721b3c0a9b926818450cfa3db31"
+ integrity sha512-W6zNLGQqfrp/EoPD0bhb9n7OobP+RHpmvVzpA+Z/IuU3Q63njJM24hmT0GYboovWcDtFmnIJC9wcyx4RVPQscw==
+ dependencies:
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+ commander "^5.1.0"
+ joi "^17.6.0"
+ react-helmet-async "^1.3.0"
+ utility-types "^3.10.0"
+ webpack "^5.73.0"
+ webpack-merge "^5.8.0"
+
+"@docusaurus/utils-common@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.4.3.tgz#30656c39ef1ce7e002af7ba39ea08330f58efcfb"
+ integrity sha512-/jascp4GbLQCPVmcGkPzEQjNaAk3ADVfMtudk49Ggb+131B1WDD6HqlSmDf8MxGdy7Dja2gc+StHf01kiWoTDQ==
+ dependencies:
+ tslib "^2.4.0"
+
+"@docusaurus/utils-validation@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.4.3.tgz#8122c394feef3e96c73f6433987837ec206a63fb"
+ integrity sha512-G2+Vt3WR5E/9drAobP+hhZQMaswRwDlp6qOMi7o7ZypB+VO7N//DZWhZEwhcRGepMDJGQEwtPv7UxtYwPL9PBw==
+ dependencies:
+ "@docusaurus/logger" "2.4.3"
+ "@docusaurus/utils" "2.4.3"
+ joi "^17.6.0"
+ js-yaml "^4.1.0"
+ tslib "^2.4.0"
+
+"@docusaurus/utils@2.4.3":
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.4.3.tgz#52b000d989380a2125831b84e3a7327bef471e89"
+ integrity sha512-fKcXsjrD86Smxv8Pt0TBFqYieZZCPh4cbf9oszUq/AMhZn3ujwpKaVYZACPX8mmjtYx0JOgNx52CREBfiGQB4A==
+ dependencies:
+ "@docusaurus/logger" "2.4.3"
+ "@svgr/webpack" "^6.2.1"
+ escape-string-regexp "^4.0.0"
+ file-loader "^6.2.0"
+ fs-extra "^10.1.0"
+ github-slugger "^1.4.0"
+ globby "^11.1.0"
+ gray-matter "^4.0.3"
+ js-yaml "^4.1.0"
+ lodash "^4.17.21"
+ micromatch "^4.0.5"
+ resolve-pathname "^3.0.0"
+ shelljs "^0.8.5"
+ tslib "^2.4.0"
+ url-loader "^4.1.1"
+ webpack "^5.73.0"
+
+"@hapi/hoek@^9.0.0":
+ version "9.3.0"
+ resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
+ integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==
+
+"@hapi/topo@^5.0.0":
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012"
+ integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==
+ dependencies:
+ "@hapi/hoek" "^9.0.0"
+
+"@jest/schemas@^29.6.3":
+ version "29.6.3"
+ resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03"
+ integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==
+ dependencies:
+ "@sinclair/typebox" "^0.27.8"
+
+"@jest/types@^29.6.3":
+ version "29.6.3"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59"
+ integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==
+ dependencies:
+ "@jest/schemas" "^29.6.3"
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ "@types/istanbul-reports" "^3.0.0"
+ "@types/node" "*"
+ "@types/yargs" "^17.0.8"
+ chalk "^4.0.0"
+
+"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
+ integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/resolve-uri@^3.1.0":
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
+ integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
+
+"@jridgewell/set-array@^1.0.1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
+ integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
+
+"@jridgewell/source-map@^0.3.3":
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91"
+ integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
+ version "1.4.15"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+ integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
+
+"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
+ version "0.3.19"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811"
+ integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
+
+"@leichtgewicht/ip-codec@^2.0.1":
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
+ integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==
+
+"@mdx-js/mdx@^1.6.22":
+ version "1.6.22"
+ resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba"
+ integrity sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA==
+ dependencies:
+ "@babel/core" "7.12.9"
+ "@babel/plugin-syntax-jsx" "7.12.1"
+ "@babel/plugin-syntax-object-rest-spread" "7.8.3"
+ "@mdx-js/util" "1.6.22"
+ babel-plugin-apply-mdx-type-prop "1.6.22"
+ babel-plugin-extract-import-names "1.6.22"
+ camelcase-css "2.0.1"
+ detab "2.0.4"
+ hast-util-raw "6.0.1"
+ lodash.uniq "4.5.0"
+ mdast-util-to-hast "10.0.1"
+ remark-footnotes "2.0.0"
+ remark-mdx "1.6.22"
+ remark-parse "8.0.3"
+ remark-squeeze-paragraphs "4.0.0"
+ style-to-object "0.3.0"
+ unified "9.2.0"
+ unist-builder "2.0.3"
+ unist-util-visit "2.0.3"
+
+"@mdx-js/react@^1.6.22":
+ version "1.6.22"
+ resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-1.6.22.tgz#ae09b4744fddc74714ee9f9d6f17a66e77c43573"
+ integrity sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==
+
+"@mdx-js/util@1.6.22":
+ version "1.6.22"
+ resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b"
+ integrity sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==
+
+"@nodelib/fs.scandir@2.1.5":
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
+ integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
+ dependencies:
+ "@nodelib/fs.stat" "2.0.5"
+ run-parallel "^1.1.9"
+
+"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
+ integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
+
+"@nodelib/fs.walk@^1.2.3":
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
+ integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
+ dependencies:
+ "@nodelib/fs.scandir" "2.1.5"
+ fastq "^1.6.0"
+
+"@polka/url@^1.0.0-next.20":
+ version "1.0.0-next.23"
+ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.23.tgz#498e41218ab3b6a1419c735e5c6ae2c5ed609b6c"
+ integrity sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==
+
+"@sideway/address@^4.1.3":
+ version "4.1.4"
+ resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0"
+ integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==
+ dependencies:
+ "@hapi/hoek" "^9.0.0"
+
+"@sideway/formula@^3.0.1":
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f"
+ integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==
+
+"@sideway/pinpoint@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df"
+ integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
+
+"@sinclair/typebox@^0.27.8":
+ version "0.27.8"
+ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
+ integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
+
+"@sindresorhus/is@^0.14.0":
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
+ integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
+
+"@slorber/static-site-generator-webpack-plugin@^4.0.7":
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.7.tgz#fc1678bddefab014e2145cbe25b3ce4e1cfc36f3"
+ integrity sha512-Ug7x6z5lwrz0WqdnNFOMYrDQNTPAprvHLSh6+/fmml3qUiz6l5eq+2MzLKWtn/q5K5NpSiFsZTP/fck/3vjSxA==
+ dependencies:
+ eval "^0.1.8"
+ p-map "^4.0.0"
+ webpack-sources "^3.2.2"
+
+"@svgr/babel-plugin-add-jsx-attribute@^6.5.1":
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz#74a5d648bd0347bda99d82409d87b8ca80b9a1ba"
+ integrity sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==
+
+"@svgr/babel-plugin-remove-jsx-attribute@*":
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186"
+ integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==
+
+"@svgr/babel-plugin-remove-jsx-empty-expression@*":
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44"
+ integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==
+
+"@svgr/babel-plugin-replace-jsx-attribute-value@^6.5.1":
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz#fb9d22ea26d2bc5e0a44b763d4c46d5d3f596c60"
+ integrity sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg==
+
+"@svgr/babel-plugin-svg-dynamic-title@^6.5.1":
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz#01b2024a2b53ffaa5efceaa0bf3e1d5a4c520ce4"
+ integrity sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw==
+
+"@svgr/babel-plugin-svg-em-dimensions@^6.5.1":
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz#dd3fa9f5b24eb4f93bcf121c3d40ff5facecb217"
+ integrity sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA==
+
+"@svgr/babel-plugin-transform-react-native-svg@^6.5.1":
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz#1d8e945a03df65b601551097d8f5e34351d3d305"
+ integrity sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg==
+
+"@svgr/babel-plugin-transform-svg-component@^6.5.1":
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz#48620b9e590e25ff95a80f811544218d27f8a250"
+ integrity sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ==
+
+"@svgr/babel-preset@^6.5.1":
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.5.1.tgz#b90de7979c8843c5c580c7e2ec71f024b49eb828"
+ integrity sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw==
+ dependencies:
+ "@svgr/babel-plugin-add-jsx-attribute" "^6.5.1"
+ "@svgr/babel-plugin-remove-jsx-attribute" "*"
+ "@svgr/babel-plugin-remove-jsx-empty-expression" "*"
+ "@svgr/babel-plugin-replace-jsx-attribute-value" "^6.5.1"
+ "@svgr/babel-plugin-svg-dynamic-title" "^6.5.1"
+ "@svgr/babel-plugin-svg-em-dimensions" "^6.5.1"
+ "@svgr/babel-plugin-transform-react-native-svg" "^6.5.1"
+ "@svgr/babel-plugin-transform-svg-component" "^6.5.1"
+
+"@svgr/core@^6.5.1":
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.5.1.tgz#d3e8aa9dbe3fbd747f9ee4282c1c77a27410488a"
+ integrity sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==
+ dependencies:
+ "@babel/core" "^7.19.6"
+ "@svgr/babel-preset" "^6.5.1"
+ "@svgr/plugin-jsx" "^6.5.1"
+ camelcase "^6.2.0"
+ cosmiconfig "^7.0.1"
+
+"@svgr/hast-util-to-babel-ast@^6.5.1":
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz#81800bd09b5bcdb968bf6ee7c863d2288fdb80d2"
+ integrity sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw==
+ dependencies:
+ "@babel/types" "^7.20.0"
+ entities "^4.4.0"
+
+"@svgr/plugin-jsx@^6.5.1":
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz#0e30d1878e771ca753c94e69581c7971542a7072"
+ integrity sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw==
+ dependencies:
+ "@babel/core" "^7.19.6"
+ "@svgr/babel-preset" "^6.5.1"
+ "@svgr/hast-util-to-babel-ast" "^6.5.1"
+ svg-parser "^2.0.4"
+
+"@svgr/plugin-svgo@^6.5.1":
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz#0f91910e988fc0b842f88e0960c2862e022abe84"
+ integrity sha512-omvZKf8ixP9z6GWgwbtmP9qQMPX4ODXi+wzbVZgomNFsUIlHA1sf4fThdwTWSsZGgvGAG6yE+b/F5gWUkcZ/iQ==
+ dependencies:
+ cosmiconfig "^7.0.1"
+ deepmerge "^4.2.2"
+ svgo "^2.8.0"
+
+"@svgr/webpack@^6.2.1":
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-6.5.1.tgz#ecf027814fc1cb2decc29dc92f39c3cf691e40e8"
+ integrity sha512-cQ/AsnBkXPkEK8cLbv4Dm7JGXq2XrumKnL1dRpJD9rIO2fTIlJI9a1uCciYG1F2aUsox/hJQyNGbt3soDxSRkA==
+ dependencies:
+ "@babel/core" "^7.19.6"
+ "@babel/plugin-transform-react-constant-elements" "^7.18.12"
+ "@babel/preset-env" "^7.19.4"
+ "@babel/preset-react" "^7.18.6"
+ "@babel/preset-typescript" "^7.18.6"
+ "@svgr/core" "^6.5.1"
+ "@svgr/plugin-jsx" "^6.5.1"
+ "@svgr/plugin-svgo" "^6.5.1"
+
+"@szmarczak/http-timer@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
+ integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==
+ dependencies:
+ defer-to-connect "^1.0.1"
+
+"@trysound/sax@0.2.0":
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
+ integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
+
+"@types/body-parser@*":
+ version "1.19.3"
+ resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.3.tgz#fb558014374f7d9e56c8f34bab2042a3a07d25cd"
+ integrity sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ==
+ dependencies:
+ "@types/connect" "*"
+ "@types/node" "*"
+
+"@types/bonjour@^3.5.9":
+ version "3.5.11"
+ resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.11.tgz#fbaa46a1529ea5c5e46cde36e4be6a880db55b84"
+ integrity sha512-isGhjmBtLIxdHBDl2xGwUzEM8AOyOvWsADWq7rqirdi/ZQoHnLWErHvsThcEzTX8juDRiZtzp2Qkv5bgNh6mAg==
+ dependencies:
+ "@types/node" "*"
+
+"@types/connect-history-api-fallback@^1.3.5":
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.1.tgz#6e5e3602d93bda975cebc3449e1a318340af9e20"
+ integrity sha512-iaQslNbARe8fctL5Lk+DsmgWOM83lM+7FzP0eQUJs1jd3kBE8NWqBTIT2S8SqQOJjxvt2eyIjpOuYeRXq2AdMw==
+ dependencies:
+ "@types/express-serve-static-core" "*"
+ "@types/node" "*"
+
+"@types/connect@*":
+ version "3.4.36"
+ resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.36.tgz#e511558c15a39cb29bd5357eebb57bd1459cd1ab"
+ integrity sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==
+ dependencies:
+ "@types/node" "*"
+
+"@types/eslint-scope@^3.7.3":
+ version "3.7.5"
+ resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.5.tgz#e28b09dbb1d9d35fdfa8a884225f00440dfc5a3e"
+ integrity sha512-JNvhIEyxVW6EoMIFIvj93ZOywYFatlpu9deeH6eSx6PE3WHYvHaQtmHmQeNw7aA81bYGBPPQqdtBm6b1SsQMmA==
+ dependencies:
+ "@types/eslint" "*"
+ "@types/estree" "*"
+
+"@types/eslint@*":
+ version "8.44.3"
+ resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.3.tgz#96614fae4875ea6328f56de38666f582d911d962"
+ integrity sha512-iM/WfkwAhwmPff3wZuPLYiHX18HI24jU8k1ZSH7P8FHwxTjZ2P6CoX2wnF43oprR+YXJM6UUxATkNvyv/JHd+g==
+ dependencies:
+ "@types/estree" "*"
+ "@types/json-schema" "*"
+
+"@types/estree@*", "@types/estree@^1.0.0":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.2.tgz#ff02bc3dc8317cd668dfec247b750ba1f1d62453"
+ integrity sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==
+
+"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33":
+ version "4.17.37"
+ resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.37.tgz#7e4b7b59da9142138a2aaa7621f5abedce8c7320"
+ integrity sha512-ZohaCYTgGFcOP7u6aJOhY9uIZQgZ2vxC2yWoArY+FeDXlqeH66ZVBjgvg+RLVAS/DWNq4Ap9ZXu1+SUQiiWYMg==
+ dependencies:
+ "@types/node" "*"
+ "@types/qs" "*"
+ "@types/range-parser" "*"
+ "@types/send" "*"
+
+"@types/express@*", "@types/express@^4.17.13":
+ version "4.17.18"
+ resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.18.tgz#efabf5c4495c1880df1bdffee604b143b29c4a95"
+ integrity sha512-Sxv8BSLLgsBYmcnGdGjjEjqET2U+AKAdCRODmMiq02FgjwuV75Ut85DRpvFjyw/Mk0vgUOliGRU0UUmuuZHByQ==
+ dependencies:
+ "@types/body-parser" "*"
+ "@types/express-serve-static-core" "^4.17.33"
+ "@types/qs" "*"
+ "@types/serve-static" "*"
+
+"@types/hast@^2.0.0":
+ version "2.3.6"
+ resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.6.tgz#bb8b05602112a26d22868acb70c4b20984ec7086"
+ integrity sha512-47rJE80oqPmFdVDCD7IheXBrVdwuBgsYwoczFvKmwfo2Mzsnt+V9OONsYauFmICb6lQPpCuXYJWejBNs4pDJRg==
+ dependencies:
+ "@types/unist" "^2"
+
+"@types/history@^4.7.11":
+ version "4.7.11"
+ resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
+ integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
+
+"@types/html-minifier-terser@^6.0.0":
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
+ integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==
+
+"@types/http-errors@*":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.2.tgz#a86e00bbde8950364f8e7846687259ffcd96e8c2"
+ integrity sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==
+
+"@types/http-proxy@^1.17.8":
+ version "1.17.12"
+ resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.12.tgz#86e849e9eeae0362548803c37a0a1afc616bd96b"
+ integrity sha512-kQtujO08dVtQ2wXAuSFfk9ASy3sug4+ogFR8Kd8UgP8PEuc1/G/8yjYRmp//PcDNJEUKOza/MrQu15bouEUCiw==
+ dependencies:
+ "@types/node" "*"
+
+"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44"
+ integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==
+
+"@types/istanbul-lib-report@*":
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#412e0725ef41cde73bfa03e0e833eaff41e0fd63"
+ integrity sha512-gPQuzaPR5h/djlAv2apEG1HVOyj1IUs7GpfMZixU0/0KXT3pm64ylHuMUI1/Akh+sq/iikxg6Z2j+fcMDXaaTQ==
+ dependencies:
+ "@types/istanbul-lib-coverage" "*"
+
+"@types/istanbul-reports@^3.0.0":
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.2.tgz#edc8e421991a3b4df875036d381fc0a5a982f549"
+ integrity sha512-kv43F9eb3Lhj+lr/Hn6OcLCs/sSM8bt+fIaP11rCYngfV6NVjzWXJ17owQtDQTL9tQ8WSLUrGsSJ6rJz0F1w1A==
+ dependencies:
+ "@types/istanbul-lib-report" "*"
+
+"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
+ version "7.0.13"
+ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85"
+ integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==
+
+"@types/mdast@^3.0.0":
+ version "3.0.13"
+ resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.13.tgz#b7ba6e52d0faeb9c493e32c205f3831022be4e1b"
+ integrity sha512-HjiGiWedR0DVFkeNljpa6Lv4/IZU1+30VY5d747K7lBudFc3R0Ibr6yJ9lN3BE28VnZyDfLF/VB1Ql1ZIbKrmg==
+ dependencies:
+ "@types/unist" "^2"
+
+"@types/mime@*":
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.2.tgz#c1ae807f13d308ee7511a5b81c74f327028e66e8"
+ integrity sha512-Wj+fqpTLtTbG7c0tH47dkahefpLKEbB+xAZuLq7b4/IDHPl/n6VoXcyUQ2bypFlbSwvCr0y+bD4euTTqTJsPxQ==
+
+"@types/mime@^1":
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.3.tgz#bbe64987e0eb05de150c305005055c7ad784a9ce"
+ integrity sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==
+
+"@types/node@*":
+ version "20.7.1"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.7.1.tgz#06d732ead0bd5ad978ef0ea9cbdeb24dc8717514"
+ integrity sha512-LT+OIXpp2kj4E2S/p91BMe+VgGX2+lfO+XTpfXhh+bCk2LkQtHZSub8ewFBMGP5ClysPjTDFa4sMI8Q3n4T0wg==
+
+"@types/node@^17.0.5":
+ version "17.0.45"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190"
+ integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==
+
+"@types/parse-json@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
+ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
+
+"@types/parse5@^5.0.0":
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109"
+ integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==
+
+"@types/prop-types@*":
+ version "15.7.7"
+ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.7.tgz#f9361f7b87fd5d8188b2c998db0a1f47e9fb391a"
+ integrity sha512-FbtmBWCcSa2J4zL781Zf1p5YUBXQomPEcep9QZCfRfQgTxz3pJWiDFLebohZ9fFntX5ibzOkSsrJ0TEew8cAog==
+
+"@types/qs@*":
+ version "6.9.8"
+ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.8.tgz#f2a7de3c107b89b441e071d5472e6b726b4adf45"
+ integrity sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==
+
+"@types/range-parser@*":
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.5.tgz#38bd1733ae299620771bd414837ade2e57757498"
+ integrity sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==
+
+"@types/react-router-config@*", "@types/react-router-config@^5.0.6":
+ version "5.0.8"
+ resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.8.tgz#dd00654de4d79927570a4a8807c4a728feed59f3"
+ integrity sha512-zBzYZsr05V9xRG96oQ/xBXHy5+fDCX5wL7bboM0FFoOYQp9Gxmz8uvuKSkLesNWHlICl+W1l64F7fmp/KsOkuw==
+ dependencies:
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+ "@types/react-router" "^5.1.0"
+
+"@types/react-router-dom@*":
+ version "5.3.3"
+ resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83"
+ integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==
+ dependencies:
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+ "@types/react-router" "*"
+
+"@types/react-router@*", "@types/react-router@^5.1.0":
+ version "5.1.20"
+ resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.20.tgz#88eccaa122a82405ef3efbcaaa5dcdd9f021387c"
+ integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==
+ dependencies:
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+
+"@types/react@*":
+ version "18.2.23"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.23.tgz#60ad6cf4895e93bed858db0e03bcc4ff97d0410e"
+ integrity sha512-qHLW6n1q2+7KyBEYnrZpcsAmU/iiCh9WGCKgXvMxx89+TYdJWRjZohVIo9XTcoLhfX3+/hP0Pbulu3bCZQ9PSA==
+ dependencies:
+ "@types/prop-types" "*"
+ "@types/scheduler" "*"
+ csstype "^3.0.2"
+
+"@types/retry@0.12.0":
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
+ integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
+
+"@types/sax@^1.2.1":
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.5.tgz#4392799e1770d24b6dc8d0c66c8882f8e1c38b3d"
+ integrity sha512-9jWta97bBVC027/MShr3gLab8gPhKy4l6qpb+UJLF5pDm3501NvA7uvqVCW+REFtx00oTi6Cq9JzLwgq6evVgw==
+ dependencies:
+ "@types/node" "*"
+
+"@types/scheduler@*":
+ version "0.16.4"
+ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.4.tgz#fedc3e5b15c26dc18faae96bf1317487cb3658cf"
+ integrity sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==
+
+"@types/send@*":
+ version "0.17.2"
+ resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.2.tgz#af78a4495e3c2b79bfbdac3955fdd50e03cc98f2"
+ integrity sha512-aAG6yRf6r0wQ29bkS+x97BIs64ZLxeE/ARwyS6wrldMm3C1MdKwCcnnEwMC1slI8wuxJOpiUH9MioC0A0i+GJw==
+ dependencies:
+ "@types/mime" "^1"
+ "@types/node" "*"
+
+"@types/serve-index@^1.9.1":
+ version "1.9.2"
+ resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.2.tgz#cb26e775678a8526b73a5d980a147518740aaecd"
+ integrity sha512-asaEIoc6J+DbBKXtO7p2shWUpKacZOoMBEGBgPG91P8xhO53ohzHWGCs4ScZo5pQMf5ukQzVT9fhX1WzpHihig==
+ dependencies:
+ "@types/express" "*"
+
+"@types/serve-static@*", "@types/serve-static@^1.13.10":
+ version "1.15.3"
+ resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.3.tgz#2cfacfd1fd4520bbc3e292cca432d5e8e2e3ee61"
+ integrity sha512-yVRvFsEMrv7s0lGhzrggJjNOSmZCdgCjw9xWrPr/kNNLp6FaDfMC1KaYl3TSJ0c58bECwNBMoQrZJ8hA8E1eFg==
+ dependencies:
+ "@types/http-errors" "*"
+ "@types/mime" "*"
+ "@types/node" "*"
+
+"@types/sockjs@^0.3.33":
+ version "0.3.34"
+ resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.34.tgz#43e10e549b36d2ba2589278f00f81b5d7ccda167"
+ integrity sha512-R+n7qBFnm/6jinlteC9DBL5dGiDGjWAvjo4viUanpnc/dG1y7uDoacXPIQ/PQEg1fI912SMHIa014ZjRpvDw4g==
+ dependencies:
+ "@types/node" "*"
+
+"@types/unist@^2", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3":
+ version "2.0.8"
+ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.8.tgz#bb197b9639aa1a04cf464a617fe800cccd92ad5c"
+ integrity sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==
+
+"@types/ws@^8.5.5":
+ version "8.5.6"
+ resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.6.tgz#e9ad51f0ab79b9110c50916c9fcbddc36d373065"
+ integrity sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg==
+ dependencies:
+ "@types/node" "*"
+
+"@types/yargs-parser@*":
+ version "21.0.1"
+ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.1.tgz#07773d7160494d56aa882d7531aac7319ea67c3b"
+ integrity sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ==
+
+"@types/yargs@^17.0.8":
+ version "17.0.25"
+ resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.25.tgz#3edd102803c97356fb4c805b2bbaf7dfc9ab6abc"
+ integrity sha512-gy7iPgwnzNvxgAEi2bXOHWCVOG6f7xsprVJH4MjlAWeBmJ7vh/Y1kwMtUrs64ztf24zVIRCpr3n/z6gm9QIkgg==
+ dependencies:
+ "@types/yargs-parser" "*"
+
+"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24"
+ integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==
+ dependencies:
+ "@webassemblyjs/helper-numbers" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+
+"@webassemblyjs/floating-point-hex-parser@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431"
+ integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==
+
+"@webassemblyjs/helper-api-error@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768"
+ integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==
+
+"@webassemblyjs/helper-buffer@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093"
+ integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==
+
+"@webassemblyjs/helper-numbers@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5"
+ integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==
+ dependencies:
+ "@webassemblyjs/floating-point-hex-parser" "1.11.6"
+ "@webassemblyjs/helper-api-error" "1.11.6"
+ "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/helper-wasm-bytecode@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9"
+ integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==
+
+"@webassemblyjs/helper-wasm-section@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577"
+ integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-buffer" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+ "@webassemblyjs/wasm-gen" "1.11.6"
+
+"@webassemblyjs/ieee754@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a"
+ integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==
+ dependencies:
+ "@xtuc/ieee754" "^1.2.0"
+
+"@webassemblyjs/leb128@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7"
+ integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==
+ dependencies:
+ "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/utf8@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a"
+ integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==
+
+"@webassemblyjs/wasm-edit@^1.11.5":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab"
+ integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-buffer" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+ "@webassemblyjs/helper-wasm-section" "1.11.6"
+ "@webassemblyjs/wasm-gen" "1.11.6"
+ "@webassemblyjs/wasm-opt" "1.11.6"
+ "@webassemblyjs/wasm-parser" "1.11.6"
+ "@webassemblyjs/wast-printer" "1.11.6"
+
+"@webassemblyjs/wasm-gen@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268"
+ integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+ "@webassemblyjs/ieee754" "1.11.6"
+ "@webassemblyjs/leb128" "1.11.6"
+ "@webassemblyjs/utf8" "1.11.6"
+
+"@webassemblyjs/wasm-opt@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2"
+ integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-buffer" "1.11.6"
+ "@webassemblyjs/wasm-gen" "1.11.6"
+ "@webassemblyjs/wasm-parser" "1.11.6"
+
+"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1"
+ integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-api-error" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+ "@webassemblyjs/ieee754" "1.11.6"
+ "@webassemblyjs/leb128" "1.11.6"
+ "@webassemblyjs/utf8" "1.11.6"
+
+"@webassemblyjs/wast-printer@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20"
+ integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@xtuc/long" "4.2.2"
+
+"@xtuc/ieee754@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
+ integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==
+
+"@xtuc/long@4.2.2":
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
+ integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
+
+accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
+ integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
+ dependencies:
+ mime-types "~2.1.34"
+ negotiator "0.6.3"
+
+acorn-import-assertions@^1.9.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac"
+ integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==
+
+acorn-walk@^8.0.0:
+ version "8.2.0"
+ resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
+ integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
+
+acorn@^8.0.4, acorn@^8.7.1, acorn@^8.8.2:
+ version "8.10.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
+ integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
+
+address@^1.0.1, address@^1.1.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e"
+ integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==
+
+aggregate-error@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
+ integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==
+ dependencies:
+ clean-stack "^2.0.0"
+ indent-string "^4.0.0"
+
+ajv-formats@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520"
+ integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==
+ dependencies:
+ ajv "^8.0.0"
+
+ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
+ version "3.5.2"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
+ integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
+
+ajv-keywords@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16"
+ integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==
+ dependencies:
+ fast-deep-equal "^3.1.3"
+
+ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5:
+ version "6.12.6"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.4.1"
+ uri-js "^4.2.2"
+
+ajv@^8.0.0, ajv@^8.9.0:
+ version "8.12.0"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1"
+ integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ json-schema-traverse "^1.0.0"
+ require-from-string "^2.0.2"
+ uri-js "^4.2.2"
+
+algoliasearch-helper@^3.10.0:
+ version "3.14.2"
+ resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.14.2.tgz#c34cfe6cefcfecd65c60bcb8bf9b68134472d28c"
+ integrity sha512-FjDSrjvQvJT/SKMW74nPgFpsoPUwZCzGbCqbp8HhBFfSk/OvNFxzCaCmuO0p7AWeLy1gD+muFwQEkBwcl5H4pg==
+ dependencies:
+ "@algolia/events" "^4.0.1"
+
+algoliasearch@^4.12.0, algoliasearch@^4.13.1, algoliasearch@^4.19.1:
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.20.0.tgz#700c2cb66e14f8a288460036c7b2a554d0d93cf4"
+ integrity sha512-y+UHEjnOItoNy0bYO+WWmLWBlPwDjKHW6mNHrPi0NkuhpQOOEbrkwQH/wgKFDLh7qlKjzoKeiRtlpewDPDG23g==
+ dependencies:
+ "@algolia/cache-browser-local-storage" "4.20.0"
+ "@algolia/cache-common" "4.20.0"
+ "@algolia/cache-in-memory" "4.20.0"
+ "@algolia/client-account" "4.20.0"
+ "@algolia/client-analytics" "4.20.0"
+ "@algolia/client-common" "4.20.0"
+ "@algolia/client-personalization" "4.20.0"
+ "@algolia/client-search" "4.20.0"
+ "@algolia/logger-common" "4.20.0"
+ "@algolia/logger-console" "4.20.0"
+ "@algolia/requester-browser-xhr" "4.20.0"
+ "@algolia/requester-common" "4.20.0"
+ "@algolia/requester-node-http" "4.20.0"
+ "@algolia/transporter" "4.20.0"
+
+ansi-align@^3.0.0, ansi-align@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59"
+ integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==
+ dependencies:
+ string-width "^4.1.0"
+
+ansi-html-community@^0.0.8:
+ version "0.0.8"
+ resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41"
+ integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==
+
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-regex@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
+ integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
+
+ansi-styles@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
+ integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
+ dependencies:
+ color-convert "^1.9.0"
+
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+ansi-styles@^6.1.0:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
+ integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
+
+anymatch@~3.1.2:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
+ integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
+ dependencies:
+ normalize-path "^3.0.0"
+ picomatch "^2.0.4"
+
+arg@^5.0.0:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
+ integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==
+
+argparse@^1.0.7:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
+ integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
+ dependencies:
+ sprintf-js "~1.0.2"
+
+argparse@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
+ integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
+array-flatten@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+ integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
+
+array-flatten@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099"
+ integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==
+
+array-union@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
+ integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
+
+asap@~2.0.3:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
+ integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
+
+at-least-node@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
+ integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
+
+autoprefixer@^10.4.12, autoprefixer@^10.4.7:
+ version "10.4.16"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.16.tgz#fad1411024d8670880bdece3970aa72e3572feb8"
+ integrity sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==
+ dependencies:
+ browserslist "^4.21.10"
+ caniuse-lite "^1.0.30001538"
+ fraction.js "^4.3.6"
+ normalize-range "^0.1.2"
+ picocolors "^1.0.0"
+ postcss-value-parser "^4.2.0"
+
+axios@^0.25.0:
+ version "0.25.0"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a"
+ integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==
+ dependencies:
+ follow-redirects "^1.14.7"
+
+babel-loader@^8.2.5:
+ version "8.3.0"
+ resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8"
+ integrity sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==
+ dependencies:
+ find-cache-dir "^3.3.1"
+ loader-utils "^2.0.0"
+ make-dir "^3.1.0"
+ schema-utils "^2.6.5"
+
+babel-plugin-apply-mdx-type-prop@1.6.22:
+ version "1.6.22"
+ resolved "https://registry.yarnpkg.com/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.22.tgz#d216e8fd0de91de3f1478ef3231e05446bc8705b"
+ integrity sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "7.10.4"
+ "@mdx-js/util" "1.6.22"
+
+babel-plugin-dynamic-import-node@^2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
+ integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==
+ dependencies:
+ object.assign "^4.1.0"
+
+babel-plugin-extract-import-names@1.6.22:
+ version "1.6.22"
+ resolved "https://registry.yarnpkg.com/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.22.tgz#de5f9a28eb12f3eb2578bf74472204e66d1a13dc"
+ integrity sha512-yJ9BsJaISua7d8zNT7oRG1ZLBJCIdZ4PZqmH8qa9N5AK01ifk3fnkc98AXhtzE7UkfCsEumvoQWgoYLhOnJ7jQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "7.10.4"
+
+babel-plugin-polyfill-corejs2@^0.4.5:
+ version "0.4.5"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz#8097b4cb4af5b64a1d11332b6fb72ef5e64a054c"
+ integrity sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg==
+ dependencies:
+ "@babel/compat-data" "^7.22.6"
+ "@babel/helper-define-polyfill-provider" "^0.4.2"
+ semver "^6.3.1"
+
+babel-plugin-polyfill-corejs3@^0.8.3:
+ version "0.8.4"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.4.tgz#1fac2b1dcef6274e72b3c72977ed8325cb330591"
+ integrity sha512-9l//BZZsPR+5XjyJMPtZSK4jv0BsTO1zDac2GC6ygx9WLGlcsnRd1Co0B2zT5fF5Ic6BZy+9m3HNZ3QcOeDKfg==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.4.2"
+ core-js-compat "^3.32.2"
+
+babel-plugin-polyfill-regenerator@^0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz#80d0f3e1098c080c8b5a65f41e9427af692dc326"
+ integrity sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.4.2"
+
+bail@^1.0.0:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776"
+ integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==
+
+balanced-match@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+base16@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
+ integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==
+
+batch@0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
+ integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==
+
+big.js@^5.2.2:
+ version "5.2.2"
+ resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
+ integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
+
+binary-extensions@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
+ integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
+
+body-parser@1.20.1:
+ version "1.20.1"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
+ integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
+ dependencies:
+ bytes "3.1.2"
+ content-type "~1.0.4"
+ debug "2.6.9"
+ depd "2.0.0"
+ destroy "1.2.0"
+ http-errors "2.0.0"
+ iconv-lite "0.4.24"
+ on-finished "2.4.1"
+ qs "6.11.0"
+ raw-body "2.5.1"
+ type-is "~1.6.18"
+ unpipe "1.0.0"
+
+bonjour-service@^1.0.11:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.1.1.tgz#960948fa0e0153f5d26743ab15baf8e33752c135"
+ integrity sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==
+ dependencies:
+ array-flatten "^2.1.2"
+ dns-equal "^1.0.0"
+ fast-deep-equal "^3.1.3"
+ multicast-dns "^7.2.5"
+
+boolbase@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
+ integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
+
+boxen@^5.0.0:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50"
+ integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==
+ dependencies:
+ ansi-align "^3.0.0"
+ camelcase "^6.2.0"
+ chalk "^4.1.0"
+ cli-boxes "^2.2.1"
+ string-width "^4.2.2"
+ type-fest "^0.20.2"
+ widest-line "^3.1.0"
+ wrap-ansi "^7.0.0"
+
+boxen@^6.2.1:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/boxen/-/boxen-6.2.1.tgz#b098a2278b2cd2845deef2dff2efc38d329b434d"
+ integrity sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==
+ dependencies:
+ ansi-align "^3.0.1"
+ camelcase "^6.2.0"
+ chalk "^4.1.2"
+ cli-boxes "^3.0.0"
+ string-width "^5.0.1"
+ type-fest "^2.5.0"
+ widest-line "^4.0.1"
+ wrap-ansi "^8.0.1"
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+braces@^3.0.2, braces@~3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+ integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+ dependencies:
+ fill-range "^7.0.1"
+
+browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.18.1, browserslist@^4.21.10, browserslist@^4.21.4, browserslist@^4.21.9:
+ version "4.22.0"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.0.tgz#6adc8116589ccea8a99d0df79c5de2436199abdb"
+ integrity sha512-v+Jcv64L2LbfTC6OnRcaxtqJNJuQAVhZKSJfR/6hn7lhnChUXl4amwVviqN1k411BB+3rRoKMitELRn1CojeRA==
+ dependencies:
+ caniuse-lite "^1.0.30001539"
+ electron-to-chromium "^1.4.530"
+ node-releases "^2.0.13"
+ update-browserslist-db "^1.0.13"
+
+buffer-from@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
+ integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
+
+bytes@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
+ integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==
+
+bytes@3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
+ integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
+
+cacheable-request@^6.0.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
+ integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==
+ dependencies:
+ clone-response "^1.0.2"
+ get-stream "^5.1.0"
+ http-cache-semantics "^4.0.0"
+ keyv "^3.0.0"
+ lowercase-keys "^2.0.0"
+ normalize-url "^4.1.0"
+ responselike "^1.0.2"
+
+call-bind@^1.0.0, call-bind@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
+ integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
+ dependencies:
+ function-bind "^1.1.1"
+ get-intrinsic "^1.0.2"
+
+callsites@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
+ integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+
+camel-case@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a"
+ integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==
+ dependencies:
+ pascal-case "^3.1.2"
+ tslib "^2.0.3"
+
+camelcase-css@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
+ integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
+
+camelcase@^6.2.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
+ integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
+
+caniuse-api@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"
+ integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==
+ dependencies:
+ browserslist "^4.0.0"
+ caniuse-lite "^1.0.0"
+ lodash.memoize "^4.1.2"
+ lodash.uniq "^4.5.0"
+
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001539:
+ version "1.0.30001541"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001541.tgz#b1aef0fadd87fb72db4dcb55d220eae17b81cdb1"
+ integrity sha512-bLOsqxDgTqUBkzxbNlSBt8annkDpQB9NdzdTbO2ooJ+eC/IQcvDspDc058g84ejCelF7vHUx57KIOjEecOHXaw==
+
+ccount@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043"
+ integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==
+
+chalk@^2.4.2:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+ integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.3.0"
+
+chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+character-entities-legacy@^1.0.0:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1"
+ integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==
+
+character-entities@^1.0.0:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b"
+ integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==
+
+character-reference-invalid@^1.0.0:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560"
+ integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==
+
+cheerio-select@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4"
+ integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==
+ dependencies:
+ boolbase "^1.0.0"
+ css-select "^5.1.0"
+ css-what "^6.1.0"
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+ domutils "^3.0.1"
+
+cheerio@^1.0.0-rc.12, cheerio@^1.0.0-rc.9:
+ version "1.0.0-rc.12"
+ resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683"
+ integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==
+ dependencies:
+ cheerio-select "^2.1.0"
+ dom-serializer "^2.0.0"
+ domhandler "^5.0.3"
+ domutils "^3.0.1"
+ htmlparser2 "^8.0.1"
+ parse5 "^7.0.0"
+ parse5-htmlparser2-tree-adapter "^7.0.0"
+
+chokidar@^3.4.2, chokidar@^3.5.3:
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
+ integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
+ dependencies:
+ anymatch "~3.1.2"
+ braces "~3.0.2"
+ glob-parent "~5.1.2"
+ is-binary-path "~2.1.0"
+ is-glob "~4.0.1"
+ normalize-path "~3.0.0"
+ readdirp "~3.6.0"
+ optionalDependencies:
+ fsevents "~2.3.2"
+
+chrome-trace-event@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
+ integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
+
+ci-info@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
+ integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
+
+ci-info@^3.2.0:
+ version "3.8.0"
+ resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91"
+ integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==
+
+clean-css@^5.2.2, clean-css@^5.3.0:
+ version "5.3.2"
+ resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.2.tgz#70ecc7d4d4114921f5d298349ff86a31a9975224"
+ integrity sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==
+ dependencies:
+ source-map "~0.6.0"
+
+clean-stack@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
+ integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
+
+cli-boxes@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
+ integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==
+
+cli-boxes@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145"
+ integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==
+
+cli-table3@^0.6.2:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2"
+ integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==
+ dependencies:
+ string-width "^4.2.0"
+ optionalDependencies:
+ "@colors/colors" "1.5.0"
+
+clone-deep@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
+ integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
+ dependencies:
+ is-plain-object "^2.0.4"
+ kind-of "^6.0.2"
+ shallow-clone "^3.0.0"
+
+clone-response@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3"
+ integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==
+ dependencies:
+ mimic-response "^1.0.0"
+
+clsx@^1.1.1, clsx@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
+ integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
+
+collapse-white-space@^1.0.2:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287"
+ integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==
+
+color-convert@^1.9.0:
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
+ integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
+ dependencies:
+ color-name "1.1.3"
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+ integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+colord@^2.9.1:
+ version "2.9.3"
+ resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43"
+ integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
+
+colorette@^2.0.10:
+ version "2.0.20"
+ resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
+ integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
+
+combine-promises@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.2.0.tgz#5f2e68451862acf85761ded4d9e2af7769c2ca6a"
+ integrity sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==
+
+comma-separated-tokens@^1.0.0:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea"
+ integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==
+
+commander@7, commander@^7.2.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
+ integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
+
+commander@^2.20.0:
+ version "2.20.3"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
+ integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
+commander@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
+ integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
+
+commander@^8.3.0:
+ version "8.3.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
+ integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
+
+commondir@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
+ integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==
+
+compressible@~2.0.16:
+ version "2.0.18"
+ resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
+ integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
+ dependencies:
+ mime-db ">= 1.43.0 < 2"
+
+compression@^1.7.4:
+ version "1.7.4"
+ resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
+ integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
+ dependencies:
+ accepts "~1.3.5"
+ bytes "3.0.0"
+ compressible "~2.0.16"
+ debug "2.6.9"
+ on-headers "~1.0.2"
+ safe-buffer "5.1.2"
+ vary "~1.1.2"
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+ integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+
+configstore@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"
+ integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==
+ dependencies:
+ dot-prop "^5.2.0"
+ graceful-fs "^4.1.2"
+ make-dir "^3.0.0"
+ unique-string "^2.0.0"
+ write-file-atomic "^3.0.0"
+ xdg-basedir "^4.0.0"
+
+connect-history-api-fallback@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8"
+ integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==
+
+consola@^2.15.3:
+ version "2.15.3"
+ resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550"
+ integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==
+
+content-disposition@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
+ integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==
+
+content-disposition@0.5.4:
+ version "0.5.4"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
+ integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
+ dependencies:
+ safe-buffer "5.2.1"
+
+content-type@~1.0.4:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
+ integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
+
+convert-source-map@^1.7.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
+ integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
+
+convert-source-map@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
+ integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
+
+cookie-signature@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
+ integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
+
+cookie@0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
+ integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
+
+copy-text-to-clipboard@^3.0.1:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz#0202b2d9bdae30a49a53f898626dcc3b49ad960b"
+ integrity sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==
+
+copy-webpack-plugin@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a"
+ integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==
+ dependencies:
+ fast-glob "^3.2.11"
+ glob-parent "^6.0.1"
+ globby "^13.1.1"
+ normalize-path "^3.0.0"
+ schema-utils "^4.0.0"
+ serialize-javascript "^6.0.0"
+
+core-js-compat@^3.31.0, core-js-compat@^3.32.2:
+ version "3.32.2"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.32.2.tgz#8047d1a8b3ac4e639f0d4f66d4431aa3b16e004c"
+ integrity sha512-+GjlguTDINOijtVRUxrQOv3kfu9rl+qPNdX2LTbJ/ZyVTuxK+ksVSAGX1nHstu4hrv1En/uPTtWgq2gI5wt4AQ==
+ dependencies:
+ browserslist "^4.21.10"
+
+core-js-pure@^3.30.2:
+ version "3.32.2"
+ resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.32.2.tgz#b7dbdac528625cf87eb0523b532eb61551b9a6d1"
+ integrity sha512-Y2rxThOuNywTjnX/PgA5vWM6CZ9QB9sz9oGeCixV8MqXZO70z/5SHzf9EeBrEBK0PN36DnEBBu9O/aGWzKuMZQ==
+
+core-js@^3.23.3:
+ version "3.32.2"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.32.2.tgz#172fb5949ef468f93b4be7841af6ab1f21992db7"
+ integrity sha512-pxXSw1mYZPDGvTQqEc5vgIb83jGQKFGYWY76z4a7weZXUolw3G+OvpZqSRcfYOoOVUQJYEPsWeQK8pKEnUtWxQ==
+
+core-util-is@~1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
+ integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
+
+cose-base@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/cose-base/-/cose-base-1.0.3.tgz#650334b41b869578a543358b80cda7e0abe0a60a"
+ integrity sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==
+ dependencies:
+ layout-base "^1.0.0"
+
+cose-base@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/cose-base/-/cose-base-2.2.0.tgz#1c395c35b6e10bb83f9769ca8b817d614add5c01"
+ integrity sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==
+ dependencies:
+ layout-base "^2.0.0"
+
+cosmiconfig@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982"
+ integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==
+ dependencies:
+ "@types/parse-json" "^4.0.0"
+ import-fresh "^3.1.0"
+ parse-json "^5.0.0"
+ path-type "^4.0.0"
+ yaml "^1.7.2"
+
+cosmiconfig@^7.0.1:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
+ integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==
+ dependencies:
+ "@types/parse-json" "^4.0.0"
+ import-fresh "^3.2.1"
+ parse-json "^5.0.0"
+ path-type "^4.0.0"
+ yaml "^1.10.0"
+
+cosmiconfig@^8.2.0:
+ version "8.3.6"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3"
+ integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==
+ dependencies:
+ import-fresh "^3.3.0"
+ js-yaml "^4.1.0"
+ parse-json "^5.2.0"
+ path-type "^4.0.0"
+
+cross-fetch@^3.1.5:
+ version "3.1.8"
+ resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82"
+ integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==
+ dependencies:
+ node-fetch "^2.6.12"
+
+cross-spawn@^7.0.3:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
+ integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
+crypto-random-string@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
+ integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
+
+css-declaration-sorter@^6.3.1:
+ version "6.4.1"
+ resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz#28beac7c20bad7f1775be3a7129d7eae409a3a71"
+ integrity sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==
+
+css-loader@^6.7.1:
+ version "6.8.1"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.8.1.tgz#0f8f52699f60f5e679eab4ec0fcd68b8e8a50a88"
+ integrity sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==
+ dependencies:
+ icss-utils "^5.1.0"
+ postcss "^8.4.21"
+ postcss-modules-extract-imports "^3.0.0"
+ postcss-modules-local-by-default "^4.0.3"
+ postcss-modules-scope "^3.0.0"
+ postcss-modules-values "^4.0.0"
+ postcss-value-parser "^4.2.0"
+ semver "^7.3.8"
+
+css-minimizer-webpack-plugin@^4.0.0:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-4.2.2.tgz#79f6199eb5adf1ff7ba57f105e3752d15211eb35"
+ integrity sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA==
+ dependencies:
+ cssnano "^5.1.8"
+ jest-worker "^29.1.2"
+ postcss "^8.4.17"
+ schema-utils "^4.0.0"
+ serialize-javascript "^6.0.0"
+ source-map "^0.6.1"
+
+css-select@^4.1.3:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b"
+ integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==
+ dependencies:
+ boolbase "^1.0.0"
+ css-what "^6.0.1"
+ domhandler "^4.3.1"
+ domutils "^2.8.0"
+ nth-check "^2.0.1"
+
+css-select@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
+ integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==
+ dependencies:
+ boolbase "^1.0.0"
+ css-what "^6.1.0"
+ domhandler "^5.0.2"
+ domutils "^3.0.1"
+ nth-check "^2.0.1"
+
+css-tree@^1.1.2, css-tree@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d"
+ integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==
+ dependencies:
+ mdn-data "2.0.14"
+ source-map "^0.6.1"
+
+css-what@^6.0.1, css-what@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
+ integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
+
+cssesc@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
+ integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
+
+cssnano-preset-advanced@^5.3.8:
+ version "5.3.10"
+ resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.10.tgz#25558a1fbf3a871fb6429ce71e41be7f5aca6eef"
+ integrity sha512-fnYJyCS9jgMU+cmHO1rPSPf9axbQyD7iUhLO5Df6O4G+fKIOMps+ZbU0PdGFejFBBZ3Pftf18fn1eG7MAPUSWQ==
+ dependencies:
+ autoprefixer "^10.4.12"
+ cssnano-preset-default "^5.2.14"
+ postcss-discard-unused "^5.1.0"
+ postcss-merge-idents "^5.1.1"
+ postcss-reduce-idents "^5.2.0"
+ postcss-zindex "^5.1.0"
+
+cssnano-preset-default@^5.2.14:
+ version "5.2.14"
+ resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz#309def4f7b7e16d71ab2438052093330d9ab45d8"
+ integrity sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==
+ dependencies:
+ css-declaration-sorter "^6.3.1"
+ cssnano-utils "^3.1.0"
+ postcss-calc "^8.2.3"
+ postcss-colormin "^5.3.1"
+ postcss-convert-values "^5.1.3"
+ postcss-discard-comments "^5.1.2"
+ postcss-discard-duplicates "^5.1.0"
+ postcss-discard-empty "^5.1.1"
+ postcss-discard-overridden "^5.1.0"
+ postcss-merge-longhand "^5.1.7"
+ postcss-merge-rules "^5.1.4"
+ postcss-minify-font-values "^5.1.0"
+ postcss-minify-gradients "^5.1.1"
+ postcss-minify-params "^5.1.4"
+ postcss-minify-selectors "^5.2.1"
+ postcss-normalize-charset "^5.1.0"
+ postcss-normalize-display-values "^5.1.0"
+ postcss-normalize-positions "^5.1.1"
+ postcss-normalize-repeat-style "^5.1.1"
+ postcss-normalize-string "^5.1.0"
+ postcss-normalize-timing-functions "^5.1.0"
+ postcss-normalize-unicode "^5.1.1"
+ postcss-normalize-url "^5.1.0"
+ postcss-normalize-whitespace "^5.1.1"
+ postcss-ordered-values "^5.1.3"
+ postcss-reduce-initial "^5.1.2"
+ postcss-reduce-transforms "^5.1.0"
+ postcss-svgo "^5.1.0"
+ postcss-unique-selectors "^5.1.1"
+
+cssnano-utils@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861"
+ integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==
+
+cssnano@^5.1.12, cssnano@^5.1.8:
+ version "5.1.15"
+ resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.15.tgz#ded66b5480d5127fcb44dac12ea5a983755136bf"
+ integrity sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==
+ dependencies:
+ cssnano-preset-default "^5.2.14"
+ lilconfig "^2.0.3"
+ yaml "^1.10.2"
+
+csso@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529"
+ integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==
+ dependencies:
+ css-tree "^1.1.2"
+
+csstype@^3.0.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
+ integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
+
+cytoscape-cose-bilkent@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz#762fa121df9930ffeb51a495d87917c570ac209b"
+ integrity sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==
+ dependencies:
+ cose-base "^1.0.0"
+
+cytoscape-fcose@^2.1.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz#e4d6f6490df4fab58ae9cea9e5c3ab8d7472f471"
+ integrity sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==
+ dependencies:
+ cose-base "^2.2.0"
+
+cytoscape@^3.23.0:
+ version "3.26.0"
+ resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.26.0.tgz#b4c6961445fd51e1fd3cca83c3ffe924d9a8abc9"
+ integrity sha512-IV+crL+KBcrCnVVUCZW+zRRRFUZQcrtdOPXki+o4CFUWLdAEYvuZLcBSJC9EBK++suamERKzeY7roq2hdovV3w==
+ dependencies:
+ heap "^0.2.6"
+ lodash "^4.17.21"
+
+"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0:
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5"
+ integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
+ dependencies:
+ internmap "1 - 2"
+
+d3-axis@3:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322"
+ integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==
+
+d3-brush@3:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c"
+ integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==
+ dependencies:
+ d3-dispatch "1 - 3"
+ d3-drag "2 - 3"
+ d3-interpolate "1 - 3"
+ d3-selection "3"
+ d3-transition "3"
+
+d3-chord@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966"
+ integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==
+ dependencies:
+ d3-path "1 - 3"
+
+"d3-color@1 - 3", d3-color@3:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
+ integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
+
+d3-contour@4:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.2.tgz#bb92063bc8c5663acb2422f99c73cbb6c6ae3bcc"
+ integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==
+ dependencies:
+ d3-array "^3.2.0"
+
+d3-delaunay@6:
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b"
+ integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==
+ dependencies:
+ delaunator "5"
+
+"d3-dispatch@1 - 3", d3-dispatch@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e"
+ integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
+
+"d3-drag@2 - 3", d3-drag@3:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba"
+ integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==
+ dependencies:
+ d3-dispatch "1 - 3"
+ d3-selection "3"
+
+"d3-dsv@1 - 3", d3-dsv@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73"
+ integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==
+ dependencies:
+ commander "7"
+ iconv-lite "0.6"
+ rw "1"
+
+"d3-ease@1 - 3", d3-ease@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
+ integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
+
+d3-fetch@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22"
+ integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==
+ dependencies:
+ d3-dsv "1 - 3"
+
+d3-force@3:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4"
+ integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==
+ dependencies:
+ d3-dispatch "1 - 3"
+ d3-quadtree "1 - 3"
+ d3-timer "1 - 3"
+
+"d3-format@1 - 3", d3-format@3:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
+ integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
+
+d3-geo@3:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.0.tgz#74fd54e1f4cebd5185ac2039217a98d39b0a4c0e"
+ integrity sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==
+ dependencies:
+ d3-array "2.5.0 - 3"
+
+d3-hierarchy@3:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6"
+ integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==
+
+"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
+ integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
+ dependencies:
+ d3-color "1 - 3"
+
+"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526"
+ integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
+
+d3-polygon@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398"
+ integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==
+
+"d3-quadtree@1 - 3", d3-quadtree@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f"
+ integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==
+
+d3-random@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4"
+ integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==
+
+d3-scale-chromatic@3:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a"
+ integrity sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==
+ dependencies:
+ d3-color "1 - 3"
+ d3-interpolate "1 - 3"
+
+d3-scale@4:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
+ integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
+ dependencies:
+ d3-array "2.10.0 - 3"
+ d3-format "1 - 3"
+ d3-interpolate "1.2.0 - 3"
+ d3-time "2.1.1 - 3"
+ d3-time-format "2 - 4"
+
+"d3-selection@2 - 3", d3-selection@3:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31"
+ integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
+
+d3-shape@3:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5"
+ integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==
+ dependencies:
+ d3-path "^3.1.0"
+
+"d3-time-format@2 - 4", d3-time-format@4:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
+ integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
+ dependencies:
+ d3-time "1 - 3"
+
+"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7"
+ integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
+ dependencies:
+ d3-array "2 - 3"
+
+"d3-timer@1 - 3", d3-timer@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
+ integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
+
+"d3-transition@2 - 3", d3-transition@3:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f"
+ integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==
+ dependencies:
+ d3-color "1 - 3"
+ d3-dispatch "1 - 3"
+ d3-ease "1 - 3"
+ d3-interpolate "1 - 3"
+ d3-timer "1 - 3"
+
+d3-zoom@3:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3"
+ integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==
+ dependencies:
+ d3-dispatch "1 - 3"
+ d3-drag "2 - 3"
+ d3-interpolate "1 - 3"
+ d3-selection "2 - 3"
+ d3-transition "2 - 3"
+
+d3@^7.4.0, d3@^7.8.2:
+ version "7.8.5"
+ resolved "https://registry.yarnpkg.com/d3/-/d3-7.8.5.tgz#fde4b760d4486cdb6f0cc8e2cbff318af844635c"
+ integrity sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==
+ dependencies:
+ d3-array "3"
+ d3-axis "3"
+ d3-brush "3"
+ d3-chord "3"
+ d3-color "3"
+ d3-contour "4"
+ d3-delaunay "6"
+ d3-dispatch "3"
+ d3-drag "3"
+ d3-dsv "3"
+ d3-ease "3"
+ d3-fetch "3"
+ d3-force "3"
+ d3-format "3"
+ d3-geo "3"
+ d3-hierarchy "3"
+ d3-interpolate "3"
+ d3-path "3"
+ d3-polygon "3"
+ d3-quadtree "3"
+ d3-random "3"
+ d3-scale "4"
+ d3-scale-chromatic "3"
+ d3-selection "3"
+ d3-shape "3"
+ d3-time "3"
+ d3-time-format "4"
+ d3-timer "3"
+ d3-transition "3"
+ d3-zoom "3"
+
+dagre-d3-es@7.0.9:
+ version "7.0.9"
+ resolved "https://registry.yarnpkg.com/dagre-d3-es/-/dagre-d3-es-7.0.9.tgz#aca12fccd9d09955a4430029ba72ee6934542a8d"
+ integrity sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==
+ dependencies:
+ d3 "^7.8.2"
+ lodash-es "^4.17.21"
+
+dayjs@^1.11.7:
+ version "1.11.10"
+ resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0"
+ integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==
+
+debug@2.6.9, debug@^2.6.0:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+debug@4, debug@^4.1.0, debug@^4.1.1:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
+decompress-response@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
+ integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==
+ dependencies:
+ mimic-response "^1.0.0"
+
+deep-extend@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+ integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+
+deepmerge@^4.2.2:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
+ integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
+
+default-gateway@^6.0.3:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71"
+ integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==
+ dependencies:
+ execa "^5.0.0"
+
+defer-to-connect@^1.0.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
+ integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==
+
+define-data-property@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451"
+ integrity sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==
+ dependencies:
+ get-intrinsic "^1.2.1"
+ gopd "^1.0.1"
+ has-property-descriptors "^1.0.0"
+
+define-lazy-prop@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
+ integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
+
+define-properties@^1.1.4:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c"
+ integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==
+ dependencies:
+ define-data-property "^1.0.1"
+ has-property-descriptors "^1.0.0"
+ object-keys "^1.1.1"
+
+del@^6.1.1:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a"
+ integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==
+ dependencies:
+ globby "^11.0.1"
+ graceful-fs "^4.2.4"
+ is-glob "^4.0.1"
+ is-path-cwd "^2.2.0"
+ is-path-inside "^3.0.2"
+ p-map "^4.0.0"
+ rimraf "^3.0.2"
+ slash "^3.0.0"
+
+delaunator@5:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b"
+ integrity sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==
+ dependencies:
+ robust-predicates "^3.0.0"
+
+depd@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
+ integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
+depd@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
+ integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
+
+destroy@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
+ integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
+
+detab@2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.4.tgz#b927892069aff405fbb9a186fe97a44a92a94b43"
+ integrity sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g==
+ dependencies:
+ repeat-string "^1.5.4"
+
+detect-node@^2.0.4:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
+ integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
+
+detect-port-alt@^1.1.6:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275"
+ integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==
+ dependencies:
+ address "^1.0.1"
+ debug "^2.6.0"
+
+detect-port@^1.3.0:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.5.1.tgz#451ca9b6eaf20451acb0799b8ab40dff7718727b"
+ integrity sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==
+ dependencies:
+ address "^1.0.1"
+ debug "4"
+
+dir-glob@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
+ integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
+ dependencies:
+ path-type "^4.0.0"
+
+dns-equal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
+ integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==
+
+dns-packet@^5.2.2:
+ version "5.6.1"
+ resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f"
+ integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==
+ dependencies:
+ "@leichtgewicht/ip-codec" "^2.0.1"
+
+dom-converter@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
+ integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==
+ dependencies:
+ utila "~0.4"
+
+dom-serializer@^1.0.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30"
+ integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==
+ dependencies:
+ domelementtype "^2.0.1"
+ domhandler "^4.2.0"
+ entities "^2.0.0"
+
+dom-serializer@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
+ integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
+ dependencies:
+ domelementtype "^2.3.0"
+ domhandler "^5.0.2"
+ entities "^4.2.0"
+
+domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
+ integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
+
+domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c"
+ integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==
+ dependencies:
+ domelementtype "^2.2.0"
+
+domhandler@^5.0.2, domhandler@^5.0.3:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
+ integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
+ dependencies:
+ domelementtype "^2.3.0"
+
+dompurify@2.4.3:
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.3.tgz#f4133af0e6a50297fc8874e2eaedc13a3c308c03"
+ integrity sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==
+
+domutils@^2.5.2, domutils@^2.8.0:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
+ integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
+ dependencies:
+ dom-serializer "^1.0.1"
+ domelementtype "^2.2.0"
+ domhandler "^4.2.0"
+
+domutils@^3.0.1:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e"
+ integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==
+ dependencies:
+ dom-serializer "^2.0.0"
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+
+dot-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
+ integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==
+ dependencies:
+ no-case "^3.0.4"
+ tslib "^2.0.3"
+
+dot-prop@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
+ integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==
+ dependencies:
+ is-obj "^2.0.0"
+
+duplexer3@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e"
+ integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==
+
+duplexer@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
+ integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
+
+eastasianwidth@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
+ integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
+
+ee-first@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+ integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
+
+electron-to-chromium@^1.4.530:
+ version "1.4.532"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.532.tgz#44454731e26f2c8c14e88cca0d073f0761784f5e"
+ integrity sha512-piIR0QFdIGKmOJTSNg5AwxZRNWQSXlRYycqDB9Srstx4lip8KpcmRxVP6zuFWExWziHYZpJ0acX7TxqX95KBpg==
+
+elkjs@^0.8.2:
+ version "0.8.2"
+ resolved "https://registry.yarnpkg.com/elkjs/-/elkjs-0.8.2.tgz#c37763c5a3e24e042e318455e0147c912a7c248e"
+ integrity sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==
+
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+emoji-regex@^9.2.2:
+ version "9.2.2"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
+ integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
+
+emojis-list@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
+ integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
+
+emoticon@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/emoticon/-/emoticon-3.2.0.tgz#c008ca7d7620fac742fe1bf4af8ff8fed154ae7f"
+ integrity sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg==
+
+encodeurl@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+ integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
+
+end-of-stream@^1.1.0:
+ version "1.4.4"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
+ integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
+ dependencies:
+ once "^1.4.0"
+
+enhanced-resolve@^5.15.0:
+ version "5.15.0"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35"
+ integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==
+ dependencies:
+ graceful-fs "^4.2.4"
+ tapable "^2.2.0"
+
+entities@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
+ integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
+
+entities@^4.2.0, entities@^4.4.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
+ integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
+
+error-ex@^1.3.1:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
+ integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
+ dependencies:
+ is-arrayish "^0.2.1"
+
+es-module-lexer@^1.2.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.1.tgz#c1b0dd5ada807a3b3155315911f364dc4e909db1"
+ integrity sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==
+
+escalade@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+ integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+
+escape-goat@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
+ integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==
+
+escape-html@^1.0.3, escape-html@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+ integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
+
+escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+ integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
+
+escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+eslint-scope@5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
+ integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^4.1.1"
+
+esprima@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
+ integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
+
+esrecurse@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+ integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+ dependencies:
+ estraverse "^5.2.0"
+
+estraverse@^4.1.1:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
+ integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
+
+estraverse@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
+ integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+esutils@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
+ integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
+eta@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/eta/-/eta-2.2.0.tgz#eb8b5f8c4e8b6306561a455e62cd7492fe3a9b8a"
+ integrity sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==
+
+etag@~1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
+ integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
+
+eval@^0.1.8:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/eval/-/eval-0.1.8.tgz#2b903473b8cc1d1989b83a1e7923f883eb357f85"
+ integrity sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==
+ dependencies:
+ "@types/node" "*"
+ require-like ">= 0.1.1"
+
+eventemitter3@^4.0.0:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
+ integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
+
+events@^3.2.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
+ integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
+
+execa@^5.0.0:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
+ integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
+ dependencies:
+ cross-spawn "^7.0.3"
+ get-stream "^6.0.0"
+ human-signals "^2.1.0"
+ is-stream "^2.0.0"
+ merge-stream "^2.0.0"
+ npm-run-path "^4.0.1"
+ onetime "^5.1.2"
+ signal-exit "^3.0.3"
+ strip-final-newline "^2.0.0"
+
+express@^4.17.3:
+ version "4.18.2"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
+ integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
+ dependencies:
+ accepts "~1.3.8"
+ array-flatten "1.1.1"
+ body-parser "1.20.1"
+ content-disposition "0.5.4"
+ content-type "~1.0.4"
+ cookie "0.5.0"
+ cookie-signature "1.0.6"
+ debug "2.6.9"
+ depd "2.0.0"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ finalhandler "1.2.0"
+ fresh "0.5.2"
+ http-errors "2.0.0"
+ merge-descriptors "1.0.1"
+ methods "~1.1.2"
+ on-finished "2.4.1"
+ parseurl "~1.3.3"
+ path-to-regexp "0.1.7"
+ proxy-addr "~2.0.7"
+ qs "6.11.0"
+ range-parser "~1.2.1"
+ safe-buffer "5.2.1"
+ send "0.18.0"
+ serve-static "1.15.0"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
+ type-is "~1.6.18"
+ utils-merge "1.0.1"
+ vary "~1.1.2"
+
+extend-shallow@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
+ integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==
+ dependencies:
+ is-extendable "^0.1.0"
+
+extend@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
+ integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
+
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+ integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4"
+ integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.2"
+ merge2 "^1.3.0"
+ micromatch "^4.0.4"
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
+ integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+
+fast-url-parser@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d"
+ integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==
+ dependencies:
+ punycode "^1.3.2"
+
+fastq@^1.6.0:
+ version "1.15.0"
+ resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a"
+ integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==
+ dependencies:
+ reusify "^1.0.4"
+
+faye-websocket@^0.11.3:
+ version "0.11.4"
+ resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da"
+ integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==
+ dependencies:
+ websocket-driver ">=0.5.1"
+
+fbemitter@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-3.0.0.tgz#00b2a1af5411254aab416cd75f9e6289bee4bff3"
+ integrity sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==
+ dependencies:
+ fbjs "^3.0.0"
+
+fbjs-css-vars@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8"
+ integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==
+
+fbjs@^3.0.0, fbjs@^3.0.1:
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.5.tgz#aa0edb7d5caa6340011790bd9249dbef8a81128d"
+ integrity sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==
+ dependencies:
+ cross-fetch "^3.1.5"
+ fbjs-css-vars "^1.0.0"
+ loose-envify "^1.0.0"
+ object-assign "^4.1.0"
+ promise "^7.1.1"
+ setimmediate "^1.0.5"
+ ua-parser-js "^1.0.35"
+
+feed@^4.2.2:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e"
+ integrity sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==
+ dependencies:
+ xml-js "^1.6.11"
+
+file-loader@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
+ integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==
+ dependencies:
+ loader-utils "^2.0.0"
+ schema-utils "^3.0.0"
+
+filesize@^8.0.6:
+ version "8.0.7"
+ resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8"
+ integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==
+
+fill-range@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+ integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+ dependencies:
+ to-regex-range "^5.0.1"
+
+finalhandler@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32"
+ integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==
+ dependencies:
+ debug "2.6.9"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ on-finished "2.4.1"
+ parseurl "~1.3.3"
+ statuses "2.0.1"
+ unpipe "~1.0.0"
+
+find-cache-dir@^3.3.1:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b"
+ integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==
+ dependencies:
+ commondir "^1.0.1"
+ make-dir "^3.0.2"
+ pkg-dir "^4.1.0"
+
+find-up@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
+ integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
+ dependencies:
+ locate-path "^3.0.0"
+
+find-up@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
+ integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
+ dependencies:
+ locate-path "^5.0.0"
+ path-exists "^4.0.0"
+
+find-up@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
+flux@^4.0.1:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/flux/-/flux-4.0.4.tgz#9661182ea81d161ee1a6a6af10d20485ef2ac572"
+ integrity sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw==
+ dependencies:
+ fbemitter "^3.0.0"
+ fbjs "^3.0.1"
+
+follow-redirects@^1.0.0, follow-redirects@^1.14.7:
+ version "1.15.3"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a"
+ integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==
+
+fork-ts-checker-webpack-plugin@^6.5.0:
+ version "6.5.3"
+ resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz#eda2eff6e22476a2688d10661688c47f611b37f3"
+ integrity sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==
+ dependencies:
+ "@babel/code-frame" "^7.8.3"
+ "@types/json-schema" "^7.0.5"
+ chalk "^4.1.0"
+ chokidar "^3.4.2"
+ cosmiconfig "^6.0.0"
+ deepmerge "^4.2.2"
+ fs-extra "^9.0.0"
+ glob "^7.1.6"
+ memfs "^3.1.2"
+ minimatch "^3.0.4"
+ schema-utils "2.7.0"
+ semver "^7.3.2"
+ tapable "^1.0.0"
+
+forwarded@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
+ integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
+
+fraction.js@^4.3.6:
+ version "4.3.6"
+ resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.6.tgz#e9e3acec6c9a28cf7bc36cbe35eea4ceb2c5c92d"
+ integrity sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==
+
+fresh@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+ integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
+
+fs-extra@^10.1.0:
+ version "10.1.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
+ integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
+fs-extra@^9.0.0:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
+ integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
+ dependencies:
+ at-least-node "^1.0.0"
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
+fs-monkey@^1.0.4:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.5.tgz#fe450175f0db0d7ea758102e1d84096acb925788"
+ integrity sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
+
+fsevents@~2.3.2:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
+function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+ integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2:
+ version "1.0.0-beta.2"
+ resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
+ integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
+
+get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82"
+ integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==
+ dependencies:
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-proto "^1.0.1"
+ has-symbols "^1.0.3"
+
+get-own-enumerable-property-symbols@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
+ integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==
+
+get-stream@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
+ integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
+ dependencies:
+ pump "^3.0.0"
+
+get-stream@^5.1.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
+ integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
+ dependencies:
+ pump "^3.0.0"
+
+get-stream@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
+ integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
+
+github-slugger@^1.4.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.5.0.tgz#17891bbc73232051474d68bd867a34625c955f7d"
+ integrity sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==
+
+glob-parent@^5.1.2, glob-parent@~5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+ dependencies:
+ is-glob "^4.0.1"
+
+glob-parent@^6.0.1:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
+ integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
+ dependencies:
+ is-glob "^4.0.3"
+
+glob-to-regexp@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
+ integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
+
+glob@^7.0.0, glob@^7.1.3, glob@^7.1.6:
+ version "7.2.3"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
+ integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.1.1"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+global-dirs@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485"
+ integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==
+ dependencies:
+ ini "2.0.0"
+
+global-modules@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
+ integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==
+ dependencies:
+ global-prefix "^3.0.0"
+
+global-prefix@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97"
+ integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==
+ dependencies:
+ ini "^1.3.5"
+ kind-of "^6.0.2"
+ which "^1.3.1"
+
+globals@^11.1.0:
+ version "11.12.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
+ integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
+
+globby@^11.0.1, globby@^11.0.4, globby@^11.1.0:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
+ integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
+ dependencies:
+ array-union "^2.1.0"
+ dir-glob "^3.0.1"
+ fast-glob "^3.2.9"
+ ignore "^5.2.0"
+ merge2 "^1.4.1"
+ slash "^3.0.0"
+
+globby@^13.1.1:
+ version "13.2.2"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592"
+ integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==
+ dependencies:
+ dir-glob "^3.0.1"
+ fast-glob "^3.3.0"
+ ignore "^5.2.4"
+ merge2 "^1.4.1"
+ slash "^4.0.0"
+
+gopd@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
+ integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
+ dependencies:
+ get-intrinsic "^1.1.3"
+
+got@^9.6.0:
+ version "9.6.0"
+ resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
+ integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==
+ dependencies:
+ "@sindresorhus/is" "^0.14.0"
+ "@szmarczak/http-timer" "^1.1.2"
+ cacheable-request "^6.0.0"
+ decompress-response "^3.3.0"
+ duplexer3 "^0.1.4"
+ get-stream "^4.1.0"
+ lowercase-keys "^1.0.1"
+ mimic-response "^1.0.1"
+ p-cancelable "^1.0.0"
+ to-readable-stream "^1.0.0"
+ url-parse-lax "^3.0.0"
+
+graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
+ version "4.2.11"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
+ integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+
+gray-matter@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798"
+ integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==
+ dependencies:
+ js-yaml "^3.13.1"
+ kind-of "^6.0.2"
+ section-matter "^1.0.0"
+ strip-bom-string "^1.0.0"
+
+gzip-size@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462"
+ integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==
+ dependencies:
+ duplexer "^0.1.2"
+
+handle-thing@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
+ integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==
+
+has-flag@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+ integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+has-property-descriptors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861"
+ integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==
+ dependencies:
+ get-intrinsic "^1.1.1"
+
+has-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
+ integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
+
+has-symbols@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+ integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
+has-yarn@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77"
+ integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==
+
+has@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+ integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+ dependencies:
+ function-bind "^1.1.1"
+
+hast-to-hyperscript@^9.0.0:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz#9b67fd188e4c81e8ad66f803855334173920218d"
+ integrity sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA==
+ dependencies:
+ "@types/unist" "^2.0.3"
+ comma-separated-tokens "^1.0.0"
+ property-information "^5.3.0"
+ space-separated-tokens "^1.0.0"
+ style-to-object "^0.3.0"
+ unist-util-is "^4.0.0"
+ web-namespaces "^1.0.0"
+
+hast-util-from-parse5@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz#554e34abdeea25ac76f5bd950a1f0180e0b3bc2a"
+ integrity sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA==
+ dependencies:
+ "@types/parse5" "^5.0.0"
+ hastscript "^6.0.0"
+ property-information "^5.0.0"
+ vfile "^4.0.0"
+ vfile-location "^3.2.0"
+ web-namespaces "^1.0.0"
+
+hast-util-parse-selector@^2.0.0:
+ version "2.2.5"
+ resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a"
+ integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==
+
+hast-util-raw@6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-6.0.1.tgz#973b15930b7529a7b66984c98148b46526885977"
+ integrity sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ hast-util-from-parse5 "^6.0.0"
+ hast-util-to-parse5 "^6.0.0"
+ html-void-elements "^1.0.0"
+ parse5 "^6.0.0"
+ unist-util-position "^3.0.0"
+ vfile "^4.0.0"
+ web-namespaces "^1.0.0"
+ xtend "^4.0.0"
+ zwitch "^1.0.0"
+
+hast-util-to-parse5@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz#1ec44650b631d72952066cea9b1445df699f8479"
+ integrity sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ==
+ dependencies:
+ hast-to-hyperscript "^9.0.0"
+ property-information "^5.0.0"
+ web-namespaces "^1.0.0"
+ xtend "^4.0.0"
+ zwitch "^1.0.0"
+
+hastscript@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640"
+ integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==
+ dependencies:
+ "@types/hast" "^2.0.0"
+ comma-separated-tokens "^1.0.0"
+ hast-util-parse-selector "^2.0.0"
+ property-information "^5.0.0"
+ space-separated-tokens "^1.0.0"
+
+he@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
+ integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+
+heap@^0.2.6:
+ version "0.2.7"
+ resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.7.tgz#1e6adf711d3f27ce35a81fe3b7bd576c2260a8fc"
+ integrity sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==
+
+history@^4.9.0:
+ version "4.10.1"
+ resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
+ integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
+ dependencies:
+ "@babel/runtime" "^7.1.2"
+ loose-envify "^1.2.0"
+ resolve-pathname "^3.0.0"
+ tiny-invariant "^1.0.2"
+ tiny-warning "^1.0.0"
+ value-equal "^1.0.1"
+
+hoist-non-react-statics@^3.1.0:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
+ integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
+ dependencies:
+ react-is "^16.7.0"
+
+hpack.js@^2.1.6:
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"
+ integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==
+ dependencies:
+ inherits "^2.0.1"
+ obuf "^1.0.0"
+ readable-stream "^2.0.1"
+ wbuf "^1.1.0"
+
+htm@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/htm/-/htm-3.1.1.tgz#49266582be0dc66ed2235d5ea892307cc0c24b78"
+ integrity sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==
+
+html-entities@^2.3.2:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.4.0.tgz#edd0cee70402584c8c76cc2c0556db09d1f45061"
+ integrity sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==
+
+html-minifier-terser@^6.0.2, html-minifier-terser@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab"
+ integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==
+ dependencies:
+ camel-case "^4.1.2"
+ clean-css "^5.2.2"
+ commander "^8.3.0"
+ he "^1.2.0"
+ param-case "^3.0.4"
+ relateurl "^0.2.7"
+ terser "^5.10.0"
+
+html-tags@^3.2.0:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce"
+ integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==
+
+html-void-elements@^1.0.0:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483"
+ integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==
+
+html-webpack-plugin@^5.5.0:
+ version "5.5.3"
+ resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz#72270f4a78e222b5825b296e5e3e1328ad525a3e"
+ integrity sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==
+ dependencies:
+ "@types/html-minifier-terser" "^6.0.0"
+ html-minifier-terser "^6.0.2"
+ lodash "^4.17.21"
+ pretty-error "^4.0.0"
+ tapable "^2.0.0"
+
+htmlparser2@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
+ integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
+ dependencies:
+ domelementtype "^2.0.1"
+ domhandler "^4.0.0"
+ domutils "^2.5.2"
+ entities "^2.0.0"
+
+htmlparser2@^8.0.1:
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21"
+ integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
+ dependencies:
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+ domutils "^3.0.1"
+ entities "^4.4.0"
+
+http-cache-semantics@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
+ integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
+
+http-deceiver@^1.2.7:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
+ integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==
+
+http-errors@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
+ integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
+ dependencies:
+ depd "2.0.0"
+ inherits "2.0.4"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
+ toidentifier "1.0.1"
+
+http-errors@~1.6.2:
+ version "1.6.3"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
+ integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==
+ dependencies:
+ depd "~1.1.2"
+ inherits "2.0.3"
+ setprototypeof "1.1.0"
+ statuses ">= 1.4.0 < 2"
+
+http-parser-js@>=0.5.1:
+ version "0.5.8"
+ resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3"
+ integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==
+
+http-proxy-middleware@^2.0.3:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f"
+ integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==
+ dependencies:
+ "@types/http-proxy" "^1.17.8"
+ http-proxy "^1.18.1"
+ is-glob "^4.0.1"
+ is-plain-obj "^3.0.0"
+ micromatch "^4.0.2"
+
+http-proxy@^1.18.1:
+ version "1.18.1"
+ resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
+ integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
+ dependencies:
+ eventemitter3 "^4.0.0"
+ follow-redirects "^1.0.0"
+ requires-port "^1.0.0"
+
+human-signals@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
+ integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
+
+iconv-lite@0.4.24:
+ version "0.4.24"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
+ integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3"
+
+iconv-lite@0.6:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
+ integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3.0.0"
+
+icss-utils@^5.0.0, icss-utils@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
+ integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
+
+ignore@^5.2.0, ignore@^5.2.4:
+ version "5.2.4"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
+ integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
+
+image-size@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.2.tgz#d778b6d0ab75b2737c1556dd631652eb963bc486"
+ integrity sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==
+ dependencies:
+ queue "6.0.2"
+
+immer@^9.0.7:
+ version "9.0.21"
+ resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176"
+ integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==
+
+import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
+ integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
+ dependencies:
+ parent-module "^1.0.0"
+ resolve-from "^4.0.0"
+
+import-lazy@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
+ integrity sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==
+
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+ integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
+
+indent-string@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
+ integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
+
+infima@0.2.0-alpha.43:
+ version "0.2.0-alpha.43"
+ resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.43.tgz#f7aa1d7b30b6c08afef441c726bac6150228cbe0"
+ integrity sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ==
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+inherits@2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+ integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==
+
+ini@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
+ integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
+
+ini@^1.3.5, ini@~1.3.0:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
+ integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
+
+inline-style-parser@0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
+ integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==
+
+"internmap@1 - 2":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
+ integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
+
+interpret@^1.0.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
+ integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
+
+invariant@^2.2.4:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
+ integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
+ dependencies:
+ loose-envify "^1.0.0"
+
+ipaddr.js@1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
+ integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
+
+ipaddr.js@^2.0.1:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f"
+ integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==
+
+is-alphabetical@1.0.4, is-alphabetical@^1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d"
+ integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==
+
+is-alphanumerical@^1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf"
+ integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==
+ dependencies:
+ is-alphabetical "^1.0.0"
+ is-decimal "^1.0.0"
+
+is-arrayish@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
+ integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
+
+is-binary-path@~2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
+ integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+ dependencies:
+ binary-extensions "^2.0.0"
+
+is-buffer@^2.0.0:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191"
+ integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
+
+is-ci@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
+ integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==
+ dependencies:
+ ci-info "^2.0.0"
+
+is-core-module@^2.13.0:
+ version "2.13.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db"
+ integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==
+ dependencies:
+ has "^1.0.3"
+
+is-decimal@^1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5"
+ integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==
+
+is-docker@^2.0.0, is-docker@^2.1.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
+ integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
+
+is-extendable@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
+ integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==
+
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
+is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-hexadecimal@^1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7"
+ integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==
+
+is-installed-globally@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520"
+ integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==
+ dependencies:
+ global-dirs "^3.0.0"
+ is-path-inside "^3.0.2"
+
+is-npm@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8"
+ integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==
+
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+is-obj@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
+ integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==
+
+is-obj@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
+ integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
+
+is-path-cwd@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb"
+ integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==
+
+is-path-inside@^3.0.2:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
+ integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+
+is-plain-obj@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
+ integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
+
+is-plain-obj@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7"
+ integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==
+
+is-plain-object@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
+ integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
+ dependencies:
+ isobject "^3.0.1"
+
+is-plain-object@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
+ integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
+
+is-regexp@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
+ integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==
+
+is-root@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c"
+ integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==
+
+is-stream@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
+ integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
+
+is-typedarray@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+ integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
+
+is-whitespace-character@^1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7"
+ integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==
+
+is-word-character@^1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230"
+ integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==
+
+is-wsl@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
+ integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
+ dependencies:
+ is-docker "^2.0.0"
+
+is-yarn-global@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
+ integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==
+
+isarray@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
+ integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
+
+isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+ integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+isobject@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
+ integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==
+
+jest-util@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc"
+ integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==
+ dependencies:
+ "@jest/types" "^29.6.3"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ ci-info "^3.2.0"
+ graceful-fs "^4.2.9"
+ picomatch "^2.2.3"
+
+jest-worker@^27.4.5:
+ version "27.5.1"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0"
+ integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==
+ dependencies:
+ "@types/node" "*"
+ merge-stream "^2.0.0"
+ supports-color "^8.0.0"
+
+jest-worker@^29.1.2:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a"
+ integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==
+ dependencies:
+ "@types/node" "*"
+ jest-util "^29.7.0"
+ merge-stream "^2.0.0"
+ supports-color "^8.0.0"
+
+jiti@^1.18.2:
+ version "1.20.0"
+ resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.20.0.tgz#2d823b5852ee8963585c8dd8b7992ffc1ae83b42"
+ integrity sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==
+
+joi@^17.6.0:
+ version "17.10.2"
+ resolved "https://registry.yarnpkg.com/joi/-/joi-17.10.2.tgz#4ecc348aa89ede0b48335aad172e0f5591e55b29"
+ integrity sha512-hcVhjBxRNW/is3nNLdGLIjkgXetkeGc2wyhydhz8KumG23Aerk4HPjU5zaPAMRqXQFc0xNqXTC7+zQjxr0GlKA==
+ dependencies:
+ "@hapi/hoek" "^9.0.0"
+ "@hapi/topo" "^5.0.0"
+ "@sideway/address" "^4.1.3"
+ "@sideway/formula" "^3.0.1"
+ "@sideway/pinpoint" "^2.0.0"
+
+"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+js-yaml@^3.13.1:
+ version "3.14.1"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
+ integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^4.0.0"
+
+js-yaml@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
+ integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+ dependencies:
+ argparse "^2.0.1"
+
+jsesc@^2.5.1:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
+ integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
+
+jsesc@~0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
+ integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==
+
+json-buffer@3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
+ integrity sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==
+
+json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
+ integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+
+json-schema-traverse@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
+ integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+json-schema-traverse@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
+ integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
+
+json5@^2.1.2, json5@^2.2.3:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
+ integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
+
+jsonfile@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
+ integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
+ dependencies:
+ universalify "^2.0.0"
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+keyv@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
+ integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==
+ dependencies:
+ json-buffer "3.0.0"
+
+khroma@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/khroma/-/khroma-2.0.0.tgz#7577de98aed9f36c7a474c4d453d94c0d6c6588b"
+ integrity sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==
+
+kind-of@^6.0.0, kind-of@^6.0.2:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
+ integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
+
+kleur@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
+ integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
+
+latest-version@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face"
+ integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==
+ dependencies:
+ package-json "^6.3.0"
+
+launch-editor@^2.6.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.0.tgz#4c0c1a6ac126c572bd9ff9a30da1d2cae66defd7"
+ integrity sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==
+ dependencies:
+ picocolors "^1.0.0"
+ shell-quote "^1.7.3"
+
+layout-base@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/layout-base/-/layout-base-1.0.2.tgz#1291e296883c322a9dd4c5dd82063721b53e26e2"
+ integrity sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==
+
+layout-base@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/layout-base/-/layout-base-2.0.1.tgz#d0337913586c90f9c2c075292069f5c2da5dd285"
+ integrity sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==
+
+leven@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
+ integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
+
+lilconfig@^2.0.3:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
+ integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
+
+lines-and-columns@^1.1.6:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
+ integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
+
+loader-runner@^4.2.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1"
+ integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
+
+loader-utils@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
+ integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
+ dependencies:
+ big.js "^5.2.2"
+ emojis-list "^3.0.0"
+ json5 "^2.1.2"
+
+loader-utils@^3.2.0:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.1.tgz#4fb104b599daafd82ef3e1a41fb9265f87e1f576"
+ integrity sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==
+
+locate-path@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
+ integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
+ dependencies:
+ p-locate "^3.0.0"
+ path-exists "^3.0.0"
+
+locate-path@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
+ integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
+ dependencies:
+ p-locate "^4.1.0"
+
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
+lodash-es@^4.17.21:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
+ integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
+
+lodash.curry@^4.0.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170"
+ integrity sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==
+
+lodash.debounce@^4.0.8:
+ version "4.0.8"
+ resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
+ integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
+
+lodash.escape@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
+ integrity sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==
+
+lodash.flatten@^4.4.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
+ integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==
+
+lodash.flow@^3.3.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a"
+ integrity sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==
+
+lodash.invokemap@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz#1748cda5d8b0ef8369c4eb3ec54c21feba1f2d62"
+ integrity sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w==
+
+lodash.memoize@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
+ integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==
+
+lodash.pullall@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.pullall/-/lodash.pullall-4.2.0.tgz#9d98b8518b7c965b0fae4099bd9fb7df8bbf38ba"
+ integrity sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg==
+
+lodash.uniq@4.5.0, lodash.uniq@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
+ integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
+
+lodash.uniqby@^4.7.0:
+ version "4.7.0"
+ resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302"
+ integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==
+
+lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+ integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
+ dependencies:
+ js-tokens "^3.0.0 || ^4.0.0"
+
+lower-case@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
+ integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
+ dependencies:
+ tslib "^2.0.3"
+
+lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
+ integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
+
+lowercase-keys@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
+ integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
+
+lru-cache@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
+ integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
+ dependencies:
+ yallist "^3.0.2"
+
+lru-cache@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+ integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+ dependencies:
+ yallist "^4.0.0"
+
+lunr-languages@^1.4.0:
+ version "1.14.0"
+ resolved "https://registry.yarnpkg.com/lunr-languages/-/lunr-languages-1.14.0.tgz#6e97635f434631729dd0e5654daedd291cd6f2d0"
+ integrity sha512-hWUAb2KqM3L7J5bcrngszzISY4BxrXn/Xhbb9TTCJYEGqlR1nG67/M14sp09+PTIRklobrn57IAxcdcO/ZFyNA==
+
+make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
+ integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
+ dependencies:
+ semver "^6.0.0"
+
+mark.js@^8.11.1:
+ version "8.11.1"
+ resolved "https://registry.yarnpkg.com/mark.js/-/mark.js-8.11.1.tgz#180f1f9ebef8b0e638e4166ad52db879beb2ffc5"
+ integrity sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==
+
+markdown-escapes@^1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535"
+ integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==
+
+mdast-squeeze-paragraphs@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz#7c4c114679c3bee27ef10b58e2e015be79f1ef97"
+ integrity sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ==
+ dependencies:
+ unist-util-remove "^2.0.0"
+
+mdast-util-definitions@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2"
+ integrity sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==
+ dependencies:
+ unist-util-visit "^2.0.0"
+
+mdast-util-to-hast@10.0.1:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz#0cfc82089494c52d46eb0e3edb7a4eb2aea021eb"
+ integrity sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA==
+ dependencies:
+ "@types/mdast" "^3.0.0"
+ "@types/unist" "^2.0.0"
+ mdast-util-definitions "^4.0.0"
+ mdurl "^1.0.0"
+ unist-builder "^2.0.0"
+ unist-util-generated "^1.0.0"
+ unist-util-position "^3.0.0"
+ unist-util-visit "^2.0.0"
+
+mdast-util-to-string@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b"
+ integrity sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==
+
+mdn-data@2.0.14:
+ version "2.0.14"
+ resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
+ integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
+
+mdurl@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
+ integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==
+
+media-typer@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+ integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
+
+memfs@^3.1.2, memfs@^3.4.3:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6"
+ integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ==
+ dependencies:
+ fs-monkey "^1.0.4"
+
+merge-descriptors@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+ integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
+
+merge-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
+ integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+
+merge2@^1.3.0, merge2@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
+ integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+
+mermaid@^9.2.2:
+ version "9.4.3"
+ resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-9.4.3.tgz#62cf210c246b74972ea98c19837519b6f03427f2"
+ integrity sha512-TLkQEtqhRSuEHSE34lh5bCa94KATCyluAXmFnNI2PRZwOpXFeqiJWwZl+d2CcemE1RS6QbbueSSq9QIg8Uxcyw==
+ dependencies:
+ "@braintree/sanitize-url" "^6.0.0"
+ cytoscape "^3.23.0"
+ cytoscape-cose-bilkent "^4.1.0"
+ cytoscape-fcose "^2.1.0"
+ d3 "^7.4.0"
+ dagre-d3-es "7.0.9"
+ dayjs "^1.11.7"
+ dompurify "2.4.3"
+ elkjs "^0.8.2"
+ khroma "^2.0.0"
+ lodash-es "^4.17.21"
+ non-layered-tidy-tree-layout "^2.0.2"
+ stylis "^4.1.2"
+ ts-dedent "^2.2.0"
+ uuid "^9.0.0"
+ web-worker "^1.2.0"
+
+methods@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+ integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
+
+micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
+ integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
+ dependencies:
+ braces "^3.0.2"
+ picomatch "^2.3.1"
+
+mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-db@~1.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
+ integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==
+
+mime-types@2.1.18:
+ version "2.1.18"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
+ integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==
+ dependencies:
+ mime-db "~1.33.0"
+
+mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
+mime@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+ integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
+
+mimic-fn@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
+ integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+
+mimic-response@^1.0.0, mimic-response@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
+ integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
+
+mini-css-extract-plugin@^2.6.1:
+ version "2.7.6"
+ resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz#282a3d38863fddcd2e0c220aaed5b90bc156564d"
+ integrity sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==
+ dependencies:
+ schema-utils "^4.0.0"
+
+minimalistic-assert@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
+ integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
+
+minimatch@3.1.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@^1.2.0, minimist@^1.2.5:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
+ integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+
+mrmime@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27"
+ integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+ integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
+
+ms@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+ms@2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+multicast-dns@^7.2.5:
+ version "7.2.5"
+ resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced"
+ integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==
+ dependencies:
+ dns-packet "^5.2.2"
+ thunky "^1.0.2"
+
+nanoid@^3.3.6:
+ version "3.3.6"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
+ integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
+
+negotiator@0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
+ integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
+
+neo-async@^2.6.2:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
+ integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
+
+no-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
+ integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
+ dependencies:
+ lower-case "^2.0.2"
+ tslib "^2.0.3"
+
+node-emoji@^1.10.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c"
+ integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==
+ dependencies:
+ lodash "^4.17.21"
+
+node-fetch@^2.6.12:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
+ integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
+ dependencies:
+ whatwg-url "^5.0.0"
+
+node-forge@^1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
+ integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
+
+node-releases@^2.0.13:
+ version "2.0.13"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d"
+ integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==
+
+non-layered-tidy-tree-layout@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz#57d35d13c356643fc296a55fb11ac15e74da7804"
+ integrity sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==
+
+normalize-path@^3.0.0, normalize-path@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+ integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
+normalize-range@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
+ integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
+
+normalize-url@^4.1.0:
+ version "4.5.1"
+ resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
+ integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
+
+normalize-url@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
+ integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
+
+npm-run-path@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
+ integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
+ dependencies:
+ path-key "^3.0.0"
+
+nprogress@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1"
+ integrity sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==
+
+nth-check@^2.0.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
+ integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==
+ dependencies:
+ boolbase "^1.0.0"
+
+object-assign@^4.1.0, object-assign@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+ integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
+
+object-inspect@^1.9.0:
+ version "1.12.3"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9"
+ integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==
+
+object-keys@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+ integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+
+object.assign@^4.1.0:
+ version "4.1.4"
+ resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f"
+ integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ has-symbols "^1.0.3"
+ object-keys "^1.1.1"
+
+obuf@^1.0.0, obuf@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
+ integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
+
+on-finished@2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
+ integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
+ dependencies:
+ ee-first "1.1.1"
+
+on-headers@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
+ integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
+
+once@^1.3.0, once@^1.3.1, once@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+ dependencies:
+ wrappy "1"
+
+onetime@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
+ integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
+ dependencies:
+ mimic-fn "^2.1.0"
+
+open@^8.0.9, open@^8.4.0:
+ version "8.4.2"
+ resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9"
+ integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==
+ dependencies:
+ define-lazy-prop "^2.0.0"
+ is-docker "^2.1.1"
+ is-wsl "^2.2.0"
+
+opener@^1.5.2:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
+ integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
+
+p-cancelable@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
+ integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
+
+p-limit@^2.0.0, p-limit@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
+ integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
+ dependencies:
+ p-try "^2.0.0"
+
+p-limit@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
+ integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+ dependencies:
+ yocto-queue "^0.1.0"
+
+p-locate@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
+ integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
+ dependencies:
+ p-limit "^2.0.0"
+
+p-locate@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
+ integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
+ dependencies:
+ p-limit "^2.2.0"
+
+p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+ dependencies:
+ p-limit "^3.0.2"
+
+p-map@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
+ integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
+ dependencies:
+ aggregate-error "^3.0.0"
+
+p-retry@^4.5.0:
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16"
+ integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==
+ dependencies:
+ "@types/retry" "0.12.0"
+ retry "^0.13.1"
+
+p-try@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+ integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
+
+package-json@^6.3.0:
+ version "6.5.0"
+ resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0"
+ integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==
+ dependencies:
+ got "^9.6.0"
+ registry-auth-token "^4.0.0"
+ registry-url "^5.0.0"
+ semver "^6.2.0"
+
+param-case@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
+ integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==
+ dependencies:
+ dot-case "^3.0.4"
+ tslib "^2.0.3"
+
+parent-module@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
+ integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+ dependencies:
+ callsites "^3.0.0"
+
+parse-entities@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8"
+ integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==
+ dependencies:
+ character-entities "^1.0.0"
+ character-entities-legacy "^1.0.0"
+ character-reference-invalid "^1.0.0"
+ is-alphanumerical "^1.0.0"
+ is-decimal "^1.0.0"
+ is-hexadecimal "^1.0.0"
+
+parse-json@^5.0.0, parse-json@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
+ integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ error-ex "^1.3.1"
+ json-parse-even-better-errors "^2.3.0"
+ lines-and-columns "^1.1.6"
+
+parse-numeric-range@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3"
+ integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==
+
+parse5-htmlparser2-tree-adapter@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1"
+ integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==
+ dependencies:
+ domhandler "^5.0.2"
+ parse5 "^7.0.0"
+
+parse5@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
+ integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
+
+parse5@^7.0.0:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
+ integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
+ dependencies:
+ entities "^4.4.0"
+
+parseurl@~1.3.2, parseurl@~1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
+ integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
+
+pascal-case@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb"
+ integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==
+ dependencies:
+ no-case "^3.0.4"
+ tslib "^2.0.3"
+
+path-exists@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
+ integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==
+
+path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
+
+path-is-inside@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
+ integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==
+
+path-key@^3.0.0, path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+path-parse@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+ integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+path-to-regexp@0.1.7:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
+ integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
+
+path-to-regexp@2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45"
+ integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==
+
+path-to-regexp@^1.7.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
+ integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
+ dependencies:
+ isarray "0.0.1"
+
+path-type@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
+ integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
+
+picocolors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+ integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+ integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+pkg-dir@^4.1.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
+ integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
+ dependencies:
+ find-up "^4.0.0"
+
+pkg-up@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
+ integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==
+ dependencies:
+ find-up "^3.0.0"
+
+postcss-calc@^8.2.3:
+ version "8.2.4"
+ resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5"
+ integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==
+ dependencies:
+ postcss-selector-parser "^6.0.9"
+ postcss-value-parser "^4.2.0"
+
+postcss-colormin@^5.3.1:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.1.tgz#86c27c26ed6ba00d96c79e08f3ffb418d1d1988f"
+ integrity sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==
+ dependencies:
+ browserslist "^4.21.4"
+ caniuse-api "^3.0.0"
+ colord "^2.9.1"
+ postcss-value-parser "^4.2.0"
+
+postcss-convert-values@^5.1.3:
+ version "5.1.3"
+ resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz#04998bb9ba6b65aa31035d669a6af342c5f9d393"
+ integrity sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==
+ dependencies:
+ browserslist "^4.21.4"
+ postcss-value-parser "^4.2.0"
+
+postcss-discard-comments@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696"
+ integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==
+
+postcss-discard-duplicates@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848"
+ integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==
+
+postcss-discard-empty@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c"
+ integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==
+
+postcss-discard-overridden@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e"
+ integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==
+
+postcss-discard-unused@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-5.1.0.tgz#8974e9b143d887677304e558c1166d3762501142"
+ integrity sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw==
+ dependencies:
+ postcss-selector-parser "^6.0.5"
+
+postcss-loader@^7.0.0:
+ version "7.3.3"
+ resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.3.3.tgz#6da03e71a918ef49df1bb4be4c80401df8e249dd"
+ integrity sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==
+ dependencies:
+ cosmiconfig "^8.2.0"
+ jiti "^1.18.2"
+ semver "^7.3.8"
+
+postcss-merge-idents@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-5.1.1.tgz#7753817c2e0b75d0853b56f78a89771e15ca04a1"
+ integrity sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw==
+ dependencies:
+ cssnano-utils "^3.1.0"
+ postcss-value-parser "^4.2.0"
+
+postcss-merge-longhand@^5.1.7:
+ version "5.1.7"
+ resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz#24a1bdf402d9ef0e70f568f39bdc0344d568fb16"
+ integrity sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+ stylehacks "^5.1.1"
+
+postcss-merge-rules@^5.1.4:
+ version "5.1.4"
+ resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz#2f26fa5cacb75b1402e213789f6766ae5e40313c"
+ integrity sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==
+ dependencies:
+ browserslist "^4.21.4"
+ caniuse-api "^3.0.0"
+ cssnano-utils "^3.1.0"
+ postcss-selector-parser "^6.0.5"
+
+postcss-minify-font-values@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b"
+ integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-minify-gradients@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c"
+ integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==
+ dependencies:
+ colord "^2.9.1"
+ cssnano-utils "^3.1.0"
+ postcss-value-parser "^4.2.0"
+
+postcss-minify-params@^5.1.4:
+ version "5.1.4"
+ resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz#c06a6c787128b3208b38c9364cfc40c8aa5d7352"
+ integrity sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==
+ dependencies:
+ browserslist "^4.21.4"
+ cssnano-utils "^3.1.0"
+ postcss-value-parser "^4.2.0"
+
+postcss-minify-selectors@^5.2.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6"
+ integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==
+ dependencies:
+ postcss-selector-parser "^6.0.5"
+
+postcss-modules-extract-imports@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
+ integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==
+
+postcss-modules-local-by-default@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz#b08eb4f083050708998ba2c6061b50c2870ca524"
+ integrity sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==
+ dependencies:
+ icss-utils "^5.0.0"
+ postcss-selector-parser "^6.0.2"
+ postcss-value-parser "^4.1.0"
+
+postcss-modules-scope@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06"
+ integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==
+ dependencies:
+ postcss-selector-parser "^6.0.4"
+
+postcss-modules-values@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
+ integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
+ dependencies:
+ icss-utils "^5.0.0"
+
+postcss-normalize-charset@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed"
+ integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==
+
+postcss-normalize-display-values@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8"
+ integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-normalize-positions@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz#ef97279d894087b59325b45c47f1e863daefbb92"
+ integrity sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-normalize-repeat-style@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz#e9eb96805204f4766df66fd09ed2e13545420fb2"
+ integrity sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-normalize-string@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228"
+ integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-normalize-timing-functions@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb"
+ integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-normalize-unicode@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz#f67297fca3fea7f17e0d2caa40769afc487aa030"
+ integrity sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==
+ dependencies:
+ browserslist "^4.21.4"
+ postcss-value-parser "^4.2.0"
+
+postcss-normalize-url@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc"
+ integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==
+ dependencies:
+ normalize-url "^6.0.1"
+ postcss-value-parser "^4.2.0"
+
+postcss-normalize-whitespace@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa"
+ integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-ordered-values@^5.1.3:
+ version "5.1.3"
+ resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz#b6fd2bd10f937b23d86bc829c69e7732ce76ea38"
+ integrity sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==
+ dependencies:
+ cssnano-utils "^3.1.0"
+ postcss-value-parser "^4.2.0"
+
+postcss-reduce-idents@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-5.2.0.tgz#c89c11336c432ac4b28792f24778859a67dfba95"
+ integrity sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-reduce-initial@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz#798cd77b3e033eae7105c18c9d371d989e1382d6"
+ integrity sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==
+ dependencies:
+ browserslist "^4.21.4"
+ caniuse-api "^3.0.0"
+
+postcss-reduce-transforms@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9"
+ integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+
+postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9:
+ version "6.0.13"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b"
+ integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
+postcss-sort-media-queries@^4.2.1:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-4.4.1.tgz#04a5a78db3921eb78f28a1a781a2e68e65258128"
+ integrity sha512-QDESFzDDGKgpiIh4GYXsSy6sek2yAwQx1JASl5AxBtU1Lq2JfKBljIPNdil989NcSKRQX1ToiaKphImtBuhXWw==
+ dependencies:
+ sort-css-media-queries "2.1.0"
+
+postcss-svgo@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d"
+ integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==
+ dependencies:
+ postcss-value-parser "^4.2.0"
+ svgo "^2.7.0"
+
+postcss-unique-selectors@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6"
+ integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==
+ dependencies:
+ postcss-selector-parser "^6.0.5"
+
+postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
+ integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
+
+postcss-zindex@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-5.1.0.tgz#4a5c7e5ff1050bd4c01d95b1847dfdcc58a496ff"
+ integrity sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A==
+
+postcss@^8.3.11, postcss@^8.4.14, postcss@^8.4.17, postcss@^8.4.21:
+ version "8.4.30"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.30.tgz#0e0648d551a606ef2192a26da4cabafcc09c1aa7"
+ integrity sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==
+ dependencies:
+ nanoid "^3.3.6"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
+
+preact@^10.13.2:
+ version "10.18.1"
+ resolved "https://registry.yarnpkg.com/preact/-/preact-10.18.1.tgz#3b84bb305f0b05f4ad5784b981d15fcec4e105da"
+ integrity sha512-mKUD7RRkQQM6s7Rkmi7IFkoEHjuFqRQUaXamO61E6Nn7vqF/bo7EZCmSyrUnp2UWHw0O7XjZ2eeXis+m7tf4lg==
+
+prepend-http@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
+ integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==
+
+pretty-error@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6"
+ integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==
+ dependencies:
+ lodash "^4.17.20"
+ renderkid "^3.0.0"
+
+pretty-time@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e"
+ integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==
+
+prism-react-renderer@^1.3.5:
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz#786bb69aa6f73c32ba1ee813fbe17a0115435085"
+ integrity sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg==
+
+prismjs@^1.28.0:
+ version "1.29.0"
+ resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12"
+ integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==
+
+process-nextick-args@~2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+ integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
+promise@^7.1.1:
+ version "7.3.1"
+ resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
+ integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
+ dependencies:
+ asap "~2.0.3"
+
+prompts@^2.4.2:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
+ integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
+ dependencies:
+ kleur "^3.0.3"
+ sisteransi "^1.0.5"
+
+prop-types@^15.6.2, prop-types@^15.7.2:
+ version "15.8.1"
+ resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
+ integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
+ dependencies:
+ loose-envify "^1.4.0"
+ object-assign "^4.1.1"
+ react-is "^16.13.1"
+
+property-information@^5.0.0, property-information@^5.3.0:
+ version "5.6.0"
+ resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69"
+ integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==
+ dependencies:
+ xtend "^4.0.0"
+
+proxy-addr@~2.0.7:
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
+ integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
+ dependencies:
+ forwarded "0.2.0"
+ ipaddr.js "1.9.1"
+
+pump@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
+ integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+punycode@^1.3.2:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
+ integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==
+
+punycode@^2.1.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
+ integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
+
+pupa@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62"
+ integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==
+ dependencies:
+ escape-goat "^2.0.0"
+
+pure-color@^1.2.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e"
+ integrity sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==
+
+qs@6.11.0:
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
+ integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
+ dependencies:
+ side-channel "^1.0.4"
+
+queue-microtask@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
+ integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+
+queue@6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65"
+ integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==
+ dependencies:
+ inherits "~2.0.3"
+
+randombytes@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
+ integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
+ dependencies:
+ safe-buffer "^5.1.0"
+
+range-parser@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
+ integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==
+
+range-parser@^1.2.1, range-parser@~1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+ integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+
+raw-body@2.5.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
+ integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
+ dependencies:
+ bytes "3.1.2"
+ http-errors "2.0.0"
+ iconv-lite "0.4.24"
+ unpipe "1.0.0"
+
+rc@1.2.8, rc@^1.2.8:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
+ integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
+ dependencies:
+ deep-extend "^0.6.0"
+ ini "~1.3.0"
+ minimist "^1.2.0"
+ strip-json-comments "~2.0.1"
+
+react-base16-styling@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c"
+ integrity sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ==
+ dependencies:
+ base16 "^1.0.0"
+ lodash.curry "^4.0.1"
+ lodash.flow "^3.3.0"
+ pure-color "^1.2.0"
+
+react-dev-utils@^12.0.1:
+ version "12.0.1"
+ resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73"
+ integrity sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==
+ dependencies:
+ "@babel/code-frame" "^7.16.0"
+ address "^1.1.2"
+ browserslist "^4.18.1"
+ chalk "^4.1.2"
+ cross-spawn "^7.0.3"
+ detect-port-alt "^1.1.6"
+ escape-string-regexp "^4.0.0"
+ filesize "^8.0.6"
+ find-up "^5.0.0"
+ fork-ts-checker-webpack-plugin "^6.5.0"
+ global-modules "^2.0.0"
+ globby "^11.0.4"
+ gzip-size "^6.0.0"
+ immer "^9.0.7"
+ is-root "^2.1.0"
+ loader-utils "^3.2.0"
+ open "^8.4.0"
+ pkg-up "^3.1.0"
+ prompts "^2.4.2"
+ react-error-overlay "^6.0.11"
+ recursive-readdir "^2.2.2"
+ shell-quote "^1.7.3"
+ strip-ansi "^6.0.1"
+ text-table "^0.2.0"
+
+react-dom@^18.2.0:
+ version "18.2.0"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
+ integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
+ dependencies:
+ loose-envify "^1.1.0"
+ scheduler "^0.23.0"
+
+react-error-overlay@^6.0.11:
+ version "6.0.11"
+ resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
+ integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
+
+react-fast-compare@^3.2.0:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"
+ integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
+
+react-helmet-async@*, react-helmet-async@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.3.0.tgz#7bd5bf8c5c69ea9f02f6083f14ce33ef545c222e"
+ integrity sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==
+ dependencies:
+ "@babel/runtime" "^7.12.5"
+ invariant "^2.2.4"
+ prop-types "^15.7.2"
+ react-fast-compare "^3.2.0"
+ shallowequal "^1.1.0"
+
+react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
+ version "16.13.1"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
+ integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+
+react-json-view@^1.21.3:
+ version "1.21.3"
+ resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.21.3.tgz#f184209ee8f1bf374fb0c41b0813cff54549c475"
+ integrity sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==
+ dependencies:
+ flux "^4.0.1"
+ react-base16-styling "^0.6.0"
+ react-lifecycles-compat "^3.0.4"
+ react-textarea-autosize "^8.3.2"
+
+react-lifecycles-compat@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
+ integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
+
+react-loadable-ssr-addon-v5-slorber@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz#2cdc91e8a744ffdf9e3556caabeb6e4278689883"
+ integrity sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==
+ dependencies:
+ "@babel/runtime" "^7.10.3"
+
+react-router-config@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988"
+ integrity sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==
+ dependencies:
+ "@babel/runtime" "^7.1.2"
+
+react-router-dom@^5.3.3:
+ version "5.3.4"
+ resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.4.tgz#2ed62ffd88cae6db134445f4a0c0ae8b91d2e5e6"
+ integrity sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==
+ dependencies:
+ "@babel/runtime" "^7.12.13"
+ history "^4.9.0"
+ loose-envify "^1.3.1"
+ prop-types "^15.6.2"
+ react-router "5.3.4"
+ tiny-invariant "^1.0.2"
+ tiny-warning "^1.0.0"
+
+react-router@5.3.4, react-router@^5.3.3:
+ version "5.3.4"
+ resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.4.tgz#8ca252d70fcc37841e31473c7a151cf777887bb5"
+ integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==
+ dependencies:
+ "@babel/runtime" "^7.12.13"
+ history "^4.9.0"
+ hoist-non-react-statics "^3.1.0"
+ loose-envify "^1.3.1"
+ path-to-regexp "^1.7.0"
+ prop-types "^15.6.2"
+ react-is "^16.6.0"
+ tiny-invariant "^1.0.2"
+ tiny-warning "^1.0.0"
+
+react-textarea-autosize@^8.3.2:
+ version "8.5.3"
+ resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz#d1e9fe760178413891484847d3378706052dd409"
+ integrity sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==
+ dependencies:
+ "@babel/runtime" "^7.20.13"
+ use-composed-ref "^1.3.0"
+ use-latest "^1.2.1"
+
+react@^18.2.0:
+ version "18.2.0"
+ resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
+ integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
+ dependencies:
+ loose-envify "^1.1.0"
+
+readable-stream@^2.0.1:
+ version "2.3.8"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
+ integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.1.1"
+ util-deprecate "~1.0.1"
+
+readable-stream@^3.0.6:
+ version "3.6.2"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
+ integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
+readdirp@~3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
+ integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
+ dependencies:
+ picomatch "^2.2.1"
+
+reading-time@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/reading-time/-/reading-time-1.5.0.tgz#d2a7f1b6057cb2e169beaf87113cc3411b5bc5bb"
+ integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==
+
+rechoir@^0.6.2:
+ version "0.6.2"
+ resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
+ integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==
+ dependencies:
+ resolve "^1.1.6"
+
+recursive-readdir@^2.2.2:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.3.tgz#e726f328c0d69153bcabd5c322d3195252379372"
+ integrity sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==
+ dependencies:
+ minimatch "^3.0.5"
+
+regenerate-unicode-properties@^10.1.0:
+ version "10.1.1"
+ resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480"
+ integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==
+ dependencies:
+ regenerate "^1.4.2"
+
+regenerate@^1.4.2:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
+ integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
+
+regenerator-runtime@^0.14.0:
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45"
+ integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==
+
+regenerator-transform@^0.15.2:
+ version "0.15.2"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4"
+ integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==
+ dependencies:
+ "@babel/runtime" "^7.8.4"
+
+regexpu-core@^5.3.1:
+ version "5.3.2"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b"
+ integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==
+ dependencies:
+ "@babel/regjsgen" "^0.8.0"
+ regenerate "^1.4.2"
+ regenerate-unicode-properties "^10.1.0"
+ regjsparser "^0.9.1"
+ unicode-match-property-ecmascript "^2.0.0"
+ unicode-match-property-value-ecmascript "^2.1.0"
+
+registry-auth-token@^4.0.0:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.2.tgz#f02d49c3668884612ca031419491a13539e21fac"
+ integrity sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==
+ dependencies:
+ rc "1.2.8"
+
+registry-url@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009"
+ integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==
+ dependencies:
+ rc "^1.2.8"
+
+regjsparser@^0.9.1:
+ version "0.9.1"
+ resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709"
+ integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==
+ dependencies:
+ jsesc "~0.5.0"
+
+relateurl@^0.2.7:
+ version "0.2.7"
+ resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
+ integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==
+
+remark-emoji@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/remark-emoji/-/remark-emoji-2.2.0.tgz#1c702090a1525da5b80e15a8f963ef2c8236cac7"
+ integrity sha512-P3cj9s5ggsUvWw5fS2uzCHJMGuXYRb0NnZqYlNecewXt8QBU9n5vW3DUUKOhepS8F9CwdMx9B8a3i7pqFWAI5w==
+ dependencies:
+ emoticon "^3.2.0"
+ node-emoji "^1.10.0"
+ unist-util-visit "^2.0.3"
+
+remark-footnotes@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/remark-footnotes/-/remark-footnotes-2.0.0.tgz#9001c4c2ffebba55695d2dd80ffb8b82f7e6303f"
+ integrity sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ==
+
+remark-mdx@1.6.22:
+ version "1.6.22"
+ resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-1.6.22.tgz#06a8dab07dcfdd57f3373af7f86bd0e992108bbd"
+ integrity sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ==
+ dependencies:
+ "@babel/core" "7.12.9"
+ "@babel/helper-plugin-utils" "7.10.4"
+ "@babel/plugin-proposal-object-rest-spread" "7.12.1"
+ "@babel/plugin-syntax-jsx" "7.12.1"
+ "@mdx-js/util" "1.6.22"
+ is-alphabetical "1.0.4"
+ remark-parse "8.0.3"
+ unified "9.2.0"
+
+remark-parse@8.0.3:
+ version "8.0.3"
+ resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.3.tgz#9c62aa3b35b79a486454c690472906075f40c7e1"
+ integrity sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==
+ dependencies:
+ ccount "^1.0.0"
+ collapse-white-space "^1.0.2"
+ is-alphabetical "^1.0.0"
+ is-decimal "^1.0.0"
+ is-whitespace-character "^1.0.0"
+ is-word-character "^1.0.0"
+ markdown-escapes "^1.0.0"
+ parse-entities "^2.0.0"
+ repeat-string "^1.5.4"
+ state-toggle "^1.0.0"
+ trim "0.0.1"
+ trim-trailing-lines "^1.0.0"
+ unherit "^1.0.4"
+ unist-util-remove-position "^2.0.0"
+ vfile-location "^3.0.0"
+ xtend "^4.0.1"
+
+remark-squeeze-paragraphs@4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz#76eb0e085295131c84748c8e43810159c5653ead"
+ integrity sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw==
+ dependencies:
+ mdast-squeeze-paragraphs "^4.0.0"
+
+renderkid@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a"
+ integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==
+ dependencies:
+ css-select "^4.1.3"
+ dom-converter "^0.2.0"
+ htmlparser2 "^6.1.0"
+ lodash "^4.17.21"
+ strip-ansi "^6.0.1"
+
+repeat-string@^1.5.4:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
+ integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==
+
+require-from-string@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
+ integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+
+"require-like@>= 0.1.1":
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/require-like/-/require-like-0.1.2.tgz#ad6f30c13becd797010c468afa775c0c0a6b47fa"
+ integrity sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==
+
+requires-port@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+ integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
+
+resolve-from@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
+ integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
+resolve-pathname@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
+ integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
+
+resolve@^1.1.6, resolve@^1.14.2, resolve@^1.3.2:
+ version "1.22.6"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362"
+ integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==
+ dependencies:
+ is-core-module "^2.13.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
+responselike@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
+ integrity sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==
+ dependencies:
+ lowercase-keys "^1.0.0"
+
+retry@^0.13.1:
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658"
+ integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==
+
+reusify@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
+ integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+
+rimraf@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+ integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+ dependencies:
+ glob "^7.1.3"
+
+robust-predicates@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771"
+ integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==
+
+rtl-detect@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.0.4.tgz#40ae0ea7302a150b96bc75af7d749607392ecac6"
+ integrity sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ==
+
+rtlcss@^3.5.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-3.5.0.tgz#c9eb91269827a102bac7ae3115dd5d049de636c3"
+ integrity sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A==
+ dependencies:
+ find-up "^5.0.0"
+ picocolors "^1.0.0"
+ postcss "^8.3.11"
+ strip-json-comments "^3.1.1"
+
+run-parallel@^1.1.9:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
+ integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
+ dependencies:
+ queue-microtask "^1.2.2"
+
+rw@1:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
+ integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==
+
+rxjs@^7.5.4:
+ version "7.8.1"
+ resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
+ integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
+ dependencies:
+ tslib "^2.1.0"
+
+safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+ integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+sax@^1.2.4:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0"
+ integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==
+
+scheduler@^0.23.0:
+ version "0.23.0"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
+ integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
+ dependencies:
+ loose-envify "^1.1.0"
+
+schema-utils@2.7.0:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7"
+ integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==
+ dependencies:
+ "@types/json-schema" "^7.0.4"
+ ajv "^6.12.2"
+ ajv-keywords "^3.4.1"
+
+schema-utils@^2.6.5:
+ version "2.7.1"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7"
+ integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==
+ dependencies:
+ "@types/json-schema" "^7.0.5"
+ ajv "^6.12.4"
+ ajv-keywords "^3.5.2"
+
+schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe"
+ integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==
+ dependencies:
+ "@types/json-schema" "^7.0.8"
+ ajv "^6.12.5"
+ ajv-keywords "^3.5.2"
+
+schema-utils@^4.0.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b"
+ integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==
+ dependencies:
+ "@types/json-schema" "^7.0.9"
+ ajv "^8.9.0"
+ ajv-formats "^2.1.1"
+ ajv-keywords "^5.1.0"
+
+section-matter@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167"
+ integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==
+ dependencies:
+ extend-shallow "^2.0.1"
+ kind-of "^6.0.0"
+
+select-hose@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
+ integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==
+
+selfsigned@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.1.tgz#18a7613d714c0cd3385c48af0075abf3f266af61"
+ integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==
+ dependencies:
+ node-forge "^1"
+
+semver-diff@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b"
+ integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==
+ dependencies:
+ semver "^6.3.0"
+
+semver@^5.4.1:
+ version "5.7.2"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
+ integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
+
+semver@^6.0.0, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1:
+ version "6.3.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
+ integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
+
+semver@^7.3.2, semver@^7.3.4, semver@^7.3.7, semver@^7.3.8:
+ version "7.5.4"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
+ integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
+ dependencies:
+ lru-cache "^6.0.0"
+
+send@0.18.0:
+ version "0.18.0"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
+ integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==
+ dependencies:
+ debug "2.6.9"
+ depd "2.0.0"
+ destroy "1.2.0"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ fresh "0.5.2"
+ http-errors "2.0.0"
+ mime "1.6.0"
+ ms "2.1.3"
+ on-finished "2.4.1"
+ range-parser "~1.2.1"
+ statuses "2.0.1"
+
+serialize-javascript@^6.0.0, serialize-javascript@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c"
+ integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==
+ dependencies:
+ randombytes "^2.1.0"
+
+serve-handler@^6.1.3:
+ version "6.1.5"
+ resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.5.tgz#a4a0964f5c55c7e37a02a633232b6f0d6f068375"
+ integrity sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==
+ dependencies:
+ bytes "3.0.0"
+ content-disposition "0.5.2"
+ fast-url-parser "1.1.3"
+ mime-types "2.1.18"
+ minimatch "3.1.2"
+ path-is-inside "1.0.2"
+ path-to-regexp "2.2.1"
+ range-parser "1.2.0"
+
+serve-index@^1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
+ integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==
+ dependencies:
+ accepts "~1.3.4"
+ batch "0.6.1"
+ debug "2.6.9"
+ escape-html "~1.0.3"
+ http-errors "~1.6.2"
+ mime-types "~2.1.17"
+ parseurl "~1.3.2"
+
+serve-static@1.15.0:
+ version "1.15.0"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
+ integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
+ dependencies:
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ parseurl "~1.3.3"
+ send "0.18.0"
+
+setimmediate@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
+ integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
+
+setprototypeof@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
+ integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
+
+setprototypeof@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
+ integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
+
+shallow-clone@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
+ integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
+ dependencies:
+ kind-of "^6.0.2"
+
+shallowequal@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
+ integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
+
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+shell-quote@^1.7.3:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
+ integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==
+
+shelljs@^0.8.5:
+ version "0.8.5"
+ resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c"
+ integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==
+ dependencies:
+ glob "^7.0.0"
+ interpret "^1.0.0"
+ rechoir "^0.6.2"
+
+side-channel@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
+ integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
+ dependencies:
+ call-bind "^1.0.0"
+ get-intrinsic "^1.0.2"
+ object-inspect "^1.9.0"
+
+signal-exit@^3.0.2, signal-exit@^3.0.3:
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
+ integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
+
+sirv@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.3.tgz#ca5868b87205a74bef62a469ed0296abceccd446"
+ integrity sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==
+ dependencies:
+ "@polka/url" "^1.0.0-next.20"
+ mrmime "^1.0.0"
+ totalist "^3.0.0"
+
+sisteransi@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
+ integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
+
+sitemap@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-7.1.1.tgz#eeed9ad6d95499161a3eadc60f8c6dce4bea2bef"
+ integrity sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==
+ dependencies:
+ "@types/node" "^17.0.5"
+ "@types/sax" "^1.2.1"
+ arg "^5.0.0"
+ sax "^1.2.4"
+
+slash@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
+ integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
+
+slash@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
+ integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
+
+sockjs@^0.3.24:
+ version "0.3.24"
+ resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce"
+ integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==
+ dependencies:
+ faye-websocket "^0.11.3"
+ uuid "^8.3.2"
+ websocket-driver "^0.7.4"
+
+sort-css-media-queries@2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.1.0.tgz#7c85e06f79826baabb232f5560e9745d7a78c4ce"
+ integrity sha512-IeWvo8NkNiY2vVYdPa27MCQiR0MN0M80johAYFVxWWXQ44KU84WNxjslwBHmc/7ZL2ccwkM7/e6S5aiKZXm7jA==
+
+source-map-js@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+ integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
+source-map-support@~0.5.20:
+ version "0.5.21"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
+ integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
+ dependencies:
+ buffer-from "^1.0.0"
+ source-map "^0.6.0"
+
+source-map@^0.5.0:
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
+ integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
+
+source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+ integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+space-separated-tokens@^1.0.0:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899"
+ integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==
+
+spdy-transport@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31"
+ integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==
+ dependencies:
+ debug "^4.1.0"
+ detect-node "^2.0.4"
+ hpack.js "^2.1.6"
+ obuf "^1.1.2"
+ readable-stream "^3.0.6"
+ wbuf "^1.7.3"
+
+spdy@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b"
+ integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==
+ dependencies:
+ debug "^4.1.0"
+ handle-thing "^2.0.0"
+ http-deceiver "^1.2.7"
+ select-hose "^2.0.0"
+ spdy-transport "^3.0.0"
+
+sprintf-js@~1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
+ integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
+
+stable@^0.1.8:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
+ integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
+
+state-toggle@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe"
+ integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==
+
+statuses@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
+ integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
+
+"statuses@>= 1.4.0 < 2":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
+ integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
+
+std-env@^3.0.1:
+ version "3.4.3"
+ resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.4.3.tgz#326f11db518db751c83fd58574f449b7c3060910"
+ integrity sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==
+
+string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@^5.0.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
+ integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
+ dependencies:
+ eastasianwidth "^0.2.0"
+ emoji-regex "^9.2.2"
+ strip-ansi "^7.0.1"
+
+string_decoder@^1.1.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
+ integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
+ dependencies:
+ safe-buffer "~5.2.0"
+
+string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+ integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+ dependencies:
+ safe-buffer "~5.1.0"
+
+stringify-object@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629"
+ integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==
+ dependencies:
+ get-own-enumerable-property-symbols "^3.0.0"
+ is-obj "^1.0.1"
+ is-regexp "^1.0.0"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^7.0.1:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
+ integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
+ dependencies:
+ ansi-regex "^6.0.1"
+
+strip-bom-string@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92"
+ integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==
+
+strip-final-newline@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
+ integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
+
+strip-json-comments@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+ integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+ integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
+
+style-to-object@0.3.0, style-to-object@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46"
+ integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==
+ dependencies:
+ inline-style-parser "0.1.1"
+
+stylehacks@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.1.tgz#7934a34eb59d7152149fa69d6e9e56f2fc34bcc9"
+ integrity sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==
+ dependencies:
+ browserslist "^4.21.4"
+ postcss-selector-parser "^6.0.4"
+
+stylis@^4.1.2:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c"
+ integrity sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==
+
+supports-color@^5.3.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+ integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+ dependencies:
+ has-flag "^3.0.0"
+
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-color@^8.0.0:
+ version "8.1.1"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
+ integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-preserve-symlinks-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
+ integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+svg-parser@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5"
+ integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==
+
+svgo@^2.7.0, svgo@^2.8.0:
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24"
+ integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==
+ dependencies:
+ "@trysound/sax" "0.2.0"
+ commander "^7.2.0"
+ css-select "^4.1.3"
+ css-tree "^1.1.3"
+ csso "^4.2.0"
+ picocolors "^1.0.0"
+ stable "^0.1.8"
+
+tapable@^1.0.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
+ integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
+
+tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
+ integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
+
+terser-webpack-plugin@^5.3.3, terser-webpack-plugin@^5.3.7:
+ version "5.3.9"
+ resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1"
+ integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jest-worker "^27.4.5"
+ schema-utils "^3.1.1"
+ serialize-javascript "^6.0.1"
+ terser "^5.16.8"
+
+terser@^5.10.0, terser@^5.16.8:
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.20.0.tgz#ea42aea62578703e33def47d5c5b93c49772423e"
+ integrity sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ==
+ dependencies:
+ "@jridgewell/source-map" "^0.3.3"
+ acorn "^8.8.2"
+ commander "^2.20.0"
+ source-map-support "~0.5.20"
+
+text-table@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
+ integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
+
+thunky@^1.0.2:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
+ integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
+
+tiny-invariant@^1.0.2:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
+ integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==
+
+tiny-warning@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
+ integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
+
+to-fast-properties@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
+ integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==
+
+to-readable-stream@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771"
+ integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==
+
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
+toidentifier@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
+ integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
+
+totalist@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8"
+ integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==
+
+tr46@~0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+ integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+
+trim-trailing-lines@^1.0.0:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0"
+ integrity sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==
+
+trim@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd"
+ integrity sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==
+
+trough@^1.0.0:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
+ integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
+
+ts-dedent@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5"
+ integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==
+
+tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
+ integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
+
+type-fest@^0.20.2:
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
+ integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
+
+type-fest@^2.5.0:
+ version "2.19.0"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
+ integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
+
+type-is@~1.6.18:
+ version "1.6.18"
+ resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
+ integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
+ dependencies:
+ media-typer "0.3.0"
+ mime-types "~2.1.24"
+
+typedarray-to-buffer@^3.1.5:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
+ integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
+ dependencies:
+ is-typedarray "^1.0.0"
+
+ua-parser-js@^1.0.35:
+ version "1.0.36"
+ resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.36.tgz#a9ab6b9bd3a8efb90bb0816674b412717b7c428c"
+ integrity sha512-znuyCIXzl8ciS3+y3fHJI/2OhQIXbXw9MWC/o3qwyR+RGppjZHrM27CGFSKCJXi2Kctiz537iOu2KnXs1lMQhw==
+
+unherit@^1.0.4:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22"
+ integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==
+ dependencies:
+ inherits "^2.0.0"
+ xtend "^4.0.0"
+
+unicode-canonical-property-names-ecmascript@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"
+ integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==
+
+unicode-match-property-ecmascript@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3"
+ integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==
+ dependencies:
+ unicode-canonical-property-names-ecmascript "^2.0.0"
+ unicode-property-aliases-ecmascript "^2.0.0"
+
+unicode-match-property-value-ecmascript@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0"
+ integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==
+
+unicode-property-aliases-ecmascript@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd"
+ integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==
+
+unified@9.2.0:
+ version "9.2.0"
+ resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.0.tgz#67a62c627c40589edebbf60f53edfd4d822027f8"
+ integrity sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==
+ dependencies:
+ bail "^1.0.0"
+ extend "^3.0.0"
+ is-buffer "^2.0.0"
+ is-plain-obj "^2.0.0"
+ trough "^1.0.0"
+ vfile "^4.0.0"
+
+unified@^9.2.2:
+ version "9.2.2"
+ resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.2.tgz#67649a1abfc3ab85d2969502902775eb03146975"
+ integrity sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==
+ dependencies:
+ bail "^1.0.0"
+ extend "^3.0.0"
+ is-buffer "^2.0.0"
+ is-plain-obj "^2.0.0"
+ trough "^1.0.0"
+ vfile "^4.0.0"
+
+unique-string@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d"
+ integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==
+ dependencies:
+ crypto-random-string "^2.0.0"
+
+unist-builder@2.0.3, unist-builder@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436"
+ integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==
+
+unist-util-generated@^1.0.0:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b"
+ integrity sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==
+
+unist-util-is@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797"
+ integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==
+
+unist-util-position@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47"
+ integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==
+
+unist-util-remove-position@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc"
+ integrity sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==
+ dependencies:
+ unist-util-visit "^2.0.0"
+
+unist-util-remove@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-2.1.0.tgz#b0b4738aa7ee445c402fda9328d604a02d010588"
+ integrity sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q==
+ dependencies:
+ unist-util-is "^4.0.0"
+
+unist-util-stringify-position@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da"
+ integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==
+ dependencies:
+ "@types/unist" "^2.0.2"
+
+unist-util-visit-parents@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6"
+ integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ unist-util-is "^4.0.0"
+
+unist-util-visit@2.0.3, unist-util-visit@^2.0.0, unist-util-visit@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c"
+ integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ unist-util-is "^4.0.0"
+ unist-util-visit-parents "^3.0.0"
+
+universalify@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
+ integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
+
+unpipe@1.0.0, unpipe@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+ integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
+
+update-browserslist-db@^1.0.13:
+ version "1.0.13"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4"
+ integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==
+ dependencies:
+ escalade "^3.1.1"
+ picocolors "^1.0.0"
+
+update-notifier@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9"
+ integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==
+ dependencies:
+ boxen "^5.0.0"
+ chalk "^4.1.0"
+ configstore "^5.0.1"
+ has-yarn "^2.1.0"
+ import-lazy "^2.1.0"
+ is-ci "^2.0.0"
+ is-installed-globally "^0.4.0"
+ is-npm "^5.0.0"
+ is-yarn-global "^0.3.0"
+ latest-version "^5.1.0"
+ pupa "^2.1.1"
+ semver "^7.3.4"
+ semver-diff "^3.1.1"
+ xdg-basedir "^4.0.0"
+
+uri-js@^4.2.2:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
+ integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+ dependencies:
+ punycode "^2.1.0"
+
+url-loader@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2"
+ integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==
+ dependencies:
+ loader-utils "^2.0.0"
+ mime-types "^2.1.27"
+ schema-utils "^3.0.0"
+
+url-parse-lax@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
+ integrity sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==
+ dependencies:
+ prepend-http "^2.0.0"
+
+use-composed-ref@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda"
+ integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==
+
+use-isomorphic-layout-effect@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb"
+ integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==
+
+use-latest@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.1.tgz#d13dfb4b08c28e3e33991546a2cee53e14038cf2"
+ integrity sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==
+ dependencies:
+ use-isomorphic-layout-effect "^1.1.1"
+
+use-sync-external-store@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
+ integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
+
+util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+ integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+
+utila@~0.4:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
+ integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==
+
+utility-types@^3.10.0:
+ version "3.10.0"
+ resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b"
+ integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==
+
+utils-merge@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+ integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
+
+uuid@^8.3.2:
+ version "8.3.2"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
+ integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
+
+uuid@^9.0.0:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
+ integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
+
+value-equal@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
+ integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
+
+vary@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+ integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
+
+vfile-location@^3.0.0, vfile-location@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.2.0.tgz#d8e41fbcbd406063669ebf6c33d56ae8721d0f3c"
+ integrity sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA==
+
+vfile-message@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a"
+ integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ unist-util-stringify-position "^2.0.0"
+
+vfile@^4.0.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624"
+ integrity sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ is-buffer "^2.0.0"
+ unist-util-stringify-position "^2.0.0"
+ vfile-message "^2.0.0"
+
+wait-on@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.1.tgz#16bbc4d1e4ebdd41c5b4e63a2e16dbd1f4e5601e"
+ integrity sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==
+ dependencies:
+ axios "^0.25.0"
+ joi "^17.6.0"
+ lodash "^4.17.21"
+ minimist "^1.2.5"
+ rxjs "^7.5.4"
+
+watchpack@^2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
+ integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
+ dependencies:
+ glob-to-regexp "^0.4.1"
+ graceful-fs "^4.1.2"
+
+wbuf@^1.1.0, wbuf@^1.7.3:
+ version "1.7.3"
+ resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df"
+ integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==
+ dependencies:
+ minimalistic-assert "^1.0.0"
+
+web-namespaces@^1.0.0:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec"
+ integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==
+
+web-worker@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.2.0.tgz#5d85a04a7fbc1e7db58f66595d7a3ac7c9c180da"
+ integrity sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==
+
+webidl-conversions@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+ integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
+
+webpack-bundle-analyzer@^4.5.0:
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.1.tgz#d00bbf3f17500c10985084f22f1a2bf45cb2f09d"
+ integrity sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w==
+ dependencies:
+ "@discoveryjs/json-ext" "0.5.7"
+ acorn "^8.0.4"
+ acorn-walk "^8.0.0"
+ commander "^7.2.0"
+ escape-string-regexp "^4.0.0"
+ gzip-size "^6.0.0"
+ is-plain-object "^5.0.0"
+ lodash.debounce "^4.0.8"
+ lodash.escape "^4.0.1"
+ lodash.flatten "^4.4.0"
+ lodash.invokemap "^4.6.0"
+ lodash.pullall "^4.2.0"
+ lodash.uniqby "^4.7.0"
+ opener "^1.5.2"
+ picocolors "^1.0.0"
+ sirv "^2.0.3"
+ ws "^7.3.1"
+
+webpack-dev-middleware@^5.3.1:
+ version "5.3.3"
+ resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f"
+ integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==
+ dependencies:
+ colorette "^2.0.10"
+ memfs "^3.4.3"
+ mime-types "^2.1.31"
+ range-parser "^1.2.1"
+ schema-utils "^4.0.0"
+
+webpack-dev-server@^4.9.3:
+ version "4.15.1"
+ resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz#8944b29c12760b3a45bdaa70799b17cb91b03df7"
+ integrity sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==
+ dependencies:
+ "@types/bonjour" "^3.5.9"
+ "@types/connect-history-api-fallback" "^1.3.5"
+ "@types/express" "^4.17.13"
+ "@types/serve-index" "^1.9.1"
+ "@types/serve-static" "^1.13.10"
+ "@types/sockjs" "^0.3.33"
+ "@types/ws" "^8.5.5"
+ ansi-html-community "^0.0.8"
+ bonjour-service "^1.0.11"
+ chokidar "^3.5.3"
+ colorette "^2.0.10"
+ compression "^1.7.4"
+ connect-history-api-fallback "^2.0.0"
+ default-gateway "^6.0.3"
+ express "^4.17.3"
+ graceful-fs "^4.2.6"
+ html-entities "^2.3.2"
+ http-proxy-middleware "^2.0.3"
+ ipaddr.js "^2.0.1"
+ launch-editor "^2.6.0"
+ open "^8.0.9"
+ p-retry "^4.5.0"
+ rimraf "^3.0.2"
+ schema-utils "^4.0.0"
+ selfsigned "^2.1.1"
+ serve-index "^1.9.1"
+ sockjs "^0.3.24"
+ spdy "^4.0.2"
+ webpack-dev-middleware "^5.3.1"
+ ws "^8.13.0"
+
+webpack-merge@^5.8.0:
+ version "5.9.0"
+ resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.9.0.tgz#dc160a1c4cf512ceca515cc231669e9ddb133826"
+ integrity sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==
+ dependencies:
+ clone-deep "^4.0.1"
+ wildcard "^2.0.0"
+
+webpack-sources@^3.2.2, webpack-sources@^3.2.3:
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
+ integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
+
+webpack@^5.73.0:
+ version "5.88.2"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.2.tgz#f62b4b842f1c6ff580f3fcb2ed4f0b579f4c210e"
+ integrity sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==
+ dependencies:
+ "@types/eslint-scope" "^3.7.3"
+ "@types/estree" "^1.0.0"
+ "@webassemblyjs/ast" "^1.11.5"
+ "@webassemblyjs/wasm-edit" "^1.11.5"
+ "@webassemblyjs/wasm-parser" "^1.11.5"
+ acorn "^8.7.1"
+ acorn-import-assertions "^1.9.0"
+ browserslist "^4.14.5"
+ chrome-trace-event "^1.0.2"
+ enhanced-resolve "^5.15.0"
+ es-module-lexer "^1.2.1"
+ eslint-scope "5.1.1"
+ events "^3.2.0"
+ glob-to-regexp "^0.4.1"
+ graceful-fs "^4.2.9"
+ json-parse-even-better-errors "^2.3.1"
+ loader-runner "^4.2.0"
+ mime-types "^2.1.27"
+ neo-async "^2.6.2"
+ schema-utils "^3.2.0"
+ tapable "^2.1.1"
+ terser-webpack-plugin "^5.3.7"
+ watchpack "^2.4.0"
+ webpack-sources "^3.2.3"
+
+webpackbar@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-5.0.2.tgz#d3dd466211c73852741dfc842b7556dcbc2b0570"
+ integrity sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==
+ dependencies:
+ chalk "^4.1.0"
+ consola "^2.15.3"
+ pretty-time "^1.1.0"
+ std-env "^3.0.1"
+
+websocket-driver@>=0.5.1, websocket-driver@^0.7.4:
+ version "0.7.4"
+ resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"
+ integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==
+ dependencies:
+ http-parser-js ">=0.5.1"
+ safe-buffer ">=5.1.0"
+ websocket-extensions ">=0.1.1"
+
+websocket-extensions@>=0.1.1:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
+ integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
+
+whatwg-url@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
+ integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
+ dependencies:
+ tr46 "~0.0.3"
+ webidl-conversions "^3.0.0"
+
+which@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
+ integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
+ dependencies:
+ isexe "^2.0.0"
+
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+widest-line@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"
+ integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==
+ dependencies:
+ string-width "^4.0.0"
+
+widest-line@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2"
+ integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==
+ dependencies:
+ string-width "^5.0.1"
+
+wildcard@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67"
+ integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==
+
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrap-ansi@^8.0.1:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
+ integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
+ dependencies:
+ ansi-styles "^6.1.0"
+ string-width "^5.0.1"
+ strip-ansi "^7.0.1"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+write-file-atomic@^3.0.0:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
+ integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==
+ dependencies:
+ imurmurhash "^0.1.4"
+ is-typedarray "^1.0.0"
+ signal-exit "^3.0.2"
+ typedarray-to-buffer "^3.1.5"
+
+ws@^7.3.1:
+ version "7.5.9"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
+ integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
+
+ws@^8.13.0:
+ version "8.14.2"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f"
+ integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==
+
+xdg-basedir@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
+ integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
+
+xml-js@^1.6.11:
+ version "1.6.11"
+ resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9"
+ integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==
+ dependencies:
+ sax "^1.2.4"
+
+xtend@^4.0.0, xtend@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
+ integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
+
+yallist@^3.0.2:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
+ integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
+
+yallist@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+ integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
+yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2:
+ version "1.10.2"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
+ integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
+
+yocto-queue@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
+ integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
+
+zwitch@^1.0.0:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"
+ integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==